summaryrefslogtreecommitdiff
path: root/source/base
diff options
context:
space:
mode:
authorpepper <peppersclothescult@gmail.com>2015-01-10 21:32:32 -0800
committerpepper <peppersclothescult@gmail.com>2015-01-10 21:32:32 -0800
commitd53fa8a169832563c62262078b8d2ffe5cab8473 (patch)
treeb911d06d357d009c976709780f10e92ce915228a /source/base
first
Diffstat (limited to 'source/base')
-rw-r--r--source/base/CharString.c298
-rw-r--r--source/base/CharString.h182
-rw-r--r--source/base/Endian.c93
-rw-r--r--source/base/Endian.h89
-rw-r--r--source/base/File.c922
-rw-r--r--source/base/File.h262
-rw-r--r--source/base/LinkedList.c157
-rw-r--r--source/base/LinkedList.h98
-rw-r--r--source/base/PlatformInfo.c272
-rw-r--r--source/base/PlatformInfo.h68
-rw-r--r--source/base/PlatformInfoMac.m41
-rw-r--r--source/base/Types.h89
12 files changed, 2571 insertions, 0 deletions
diff --git a/source/base/CharString.c b/source/base/CharString.c
new file mode 100644
index 0000000..e44e4dd
--- /dev/null
+++ b/source/base/CharString.c
@@ -0,0 +1,298 @@
+//
+// CharString.c - MrsWatson
+// Created by Nik Reiman on 1/2/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "logging/EventLogger.h"
+
+CharString newCharString(void)
+{
+ return newCharStringWithCapacity(kCharStringLengthDefault);
+}
+
+CharString newCharStringWithCapacity(size_t length)
+{
+ CharString charString = (CharString)malloc(sizeof(CharStringMembers));
+ charString->capacity = length;
+ charString->data = (char *)malloc(sizeof(char) * length);
+ charStringClear(charString);
+ return charString;
+}
+
+CharString newCharStringWithCString(const char *string)
+{
+ size_t length;
+ CharString result = NULL;
+
+ length = string != NULL ? strlen(string) : 0;
+
+ if (length > kCharStringLengthLong) {
+ logError("Can't create string with length %d", length);
+ } else if (length == 0) {
+ result = newCharString();
+ } else {
+ // Add 1 to compensate for trailing null character
+ result = newCharStringWithCapacity(length + 1);
+ strncpy(result->data, string, length);
+ }
+
+ return result;
+}
+
+void charStringAppend(CharString self, const CharString string)
+{
+ charStringAppendCString(self, string->data);
+}
+
+void charStringAppendCString(CharString self, const char *string)
+{
+ size_t stringLength = strlen(string);
+ size_t selfLength = strlen(self->data);
+
+ if (stringLength + selfLength >= self->capacity) {
+ self->capacity = stringLength + selfLength + 1; // don't forget the null!
+ self->data = (char *)realloc(self->data, self->capacity);
+ strcat(self->data, string);
+ } else {
+ strcat(self->data, string);
+ }
+}
+
+void charStringClear(CharString self)
+{
+ memset(self->data, 0, self->capacity);
+}
+
+void charStringCopyCString(CharString self, const char *string)
+{
+ strncpy(self->data, string, self->capacity);
+}
+
+void charStringCopy(CharString self, const CharString string)
+{
+ strncpy(self->data, string->data, self->capacity);
+}
+
+boolByte charStringIsEmpty(const CharString self)
+{
+ return (boolByte)(self == NULL || self->data == NULL || self->data[0] == '\0');
+}
+
+boolByte charStringIsEqualTo(const CharString self, const CharString string, boolByte caseInsensitive)
+{
+ size_t comparisonSize;
+
+ if (self == NULL || string == NULL) {
+ return false;
+ }
+
+ // Only compare to the length of the smaller of the two strings
+ comparisonSize = self->capacity < string->capacity ? self->capacity : string->capacity;
+
+ if (caseInsensitive) {
+ return (boolByte)(strncasecmp(self->data, string->data, comparisonSize) == 0);
+ } else {
+ return (boolByte)(strncmp(self->data, string->data, comparisonSize) == 0);
+ }
+}
+
+boolByte charStringIsEqualToCString(const CharString self, const char *string, boolByte caseInsensitive)
+{
+ if (self == NULL || string == NULL) {
+ return false;
+ } else if (caseInsensitive) {
+ return (boolByte)(strncasecmp(self->data, string, self->capacity) == 0);
+ } else {
+ return (boolByte)(strncmp(self->data, string, self->capacity) == 0);
+ }
+}
+
+boolByte charStringIsLetter(const CharString self, const size_t index)
+{
+ const char ch = self->data[index];
+ return (boolByte)((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'));
+}
+
+boolByte charStringIsNumber(const CharString self, const size_t index)
+{
+ const char ch = self->data[index];
+ return (boolByte)(ch >= '0' && ch <= '9');
+}
+
+LinkedList charStringSplit(const CharString self, const char delimiter)
+{
+ LinkedList result = NULL;
+ char *delimiterPtr = NULL;
+ char *selfIndex = self->data;
+ CharString item = NULL;
+ size_t charsToCopy = 0;
+ boolByte done = false;
+
+ if (delimiter == '\0') {
+ logError("Cannot split string with NULL delimiter");
+ return NULL;
+ }
+
+ result = newLinkedList();
+
+ while (!done) {
+ delimiterPtr = strchr(selfIndex, delimiter);
+
+ if (delimiterPtr == NULL) {
+ done = true;
+ charsToCopy = self->data + strlen(self->data) - selfIndex;
+ } else {
+ charsToCopy = delimiterPtr - selfIndex;
+ }
+
+ if (charsToCopy > 0) {
+ item = newCharStringWithCapacity(charsToCopy + 1);
+ strncpy(item->data, selfIndex, charsToCopy);
+ linkedListAppend(result, item);
+ }
+
+ selfIndex = delimiterPtr + 1;
+ }
+
+ return result;
+}
+
+void _charStringWrap(const char *srcString, char *destString, size_t destStringSize, int indentSize, int lineLength);
+void _charStringWrap(const char *srcString, char *destString, size_t destStringSize, int indentSize, int lineLength)
+{
+ char *lineBuffer = NULL;
+ unsigned long destStringIndex = 0;
+ unsigned long srcStringIndex = 0;
+ size_t lineIndex = 0;
+ int indentIndex = 0;
+ size_t bufferLength;
+ char *newlinePosition;
+ char *lastSpacePosition;
+
+ // Sanity checks
+ if (srcString == NULL) {
+ return;
+ } else if (indentSize < 0 || indentSize > lineLength) {
+ return;
+ } else if (lineLength <= 0) {
+ return;
+ }
+
+ lineBuffer = (char *)malloc(sizeof(char) * lineLength);
+
+ while (srcStringIndex < strlen(srcString)) {
+ if (lineIndex == 0) {
+ for (indentIndex = 0; indentIndex < indentSize; indentIndex++) {
+ destString[destStringIndex++] = ' ';
+ lineIndex++;
+ }
+ }
+
+ // Clear out the line buffer, and copy a full line into it
+ memset(lineBuffer, 0, lineLength);
+ bufferLength = lineLength - lineIndex - 1; // don't forget the null!
+
+ if (bufferLength <= 0) {
+ break;
+ }
+
+ strncpy(lineBuffer, srcString + srcStringIndex, bufferLength);
+
+ // Check to see if we have copied the last line of the source string. If so, append that to
+ // the destination string and break.
+ if (bufferLength + srcStringIndex >= strlen(srcString)) {
+ strncpy(destString + destStringIndex, lineBuffer, destStringSize - destStringIndex - 1);
+ break;
+ }
+
+ // Look for any newlines in the buffer, and stop there if we find any
+ newlinePosition = strchr(lineBuffer, '\n');
+
+ if (newlinePosition != NULL) {
+ bufferLength = newlinePosition - lineBuffer + 1;
+ strncpy(destString + destStringIndex, lineBuffer, destStringSize - destStringIndex - 1);
+ destStringIndex += bufferLength;
+ srcStringIndex += bufferLength;
+ lineIndex = 0;
+ continue;
+ }
+
+ // If no newlines were found, then find the last space in this line and copy to that point
+ lastSpacePosition = strrchr(lineBuffer, ' ');
+
+ if (lastSpacePosition == NULL) {
+ // If NULL is returned here, then there are no spaces in this line. In this case, insert
+ // a hyphen at the end of the line and start a new line. Also, we need to leave room
+ // for the newline, so subtract 2 from the total buffer length.
+ bufferLength = lineLength - lineIndex - 1;
+ strncpy(destString + destStringIndex, lineBuffer, bufferLength);
+ destString[lineLength - 1] = '-';
+ // Move the destination string index ahead 1 to account for the hyphen, and the source
+ // string index back one to copy the last character from the previous line.
+ destStringIndex++;
+ srcStringIndex--;
+ } else {
+ bufferLength = lastSpacePosition - lineBuffer;
+ strncpy(destString + destStringIndex, lineBuffer, bufferLength);
+ }
+
+ // Increase string indexes and continue looping
+ destStringIndex += bufferLength;
+ destString[destStringIndex++] = '\n';
+ srcStringIndex += bufferLength + 1;
+ lineIndex = 0;
+ }
+
+ free(lineBuffer);
+}
+
+CharString charStringWrap(const CharString srcString, unsigned int indentSize)
+{
+ CharString destString;
+
+ if (srcString == NULL) {
+ return NULL;
+ }
+
+ // Allocate 2x as many characters as needed to avoid buffer overflows.
+ // Since this method is only used in "user-friendly" cases, it's ok to be
+ // a bit wasteful in the name of avoiding memory corruption. Therefore this
+ // function should *not* used for regular logging or text output.
+ destString = newCharStringWithCapacity(srcString->capacity * 2);
+ _charStringWrap(srcString->data, destString->data, destString->capacity, indentSize, TERMINAL_LINE_LENGTH);
+ return destString;
+}
+
+void freeCharString(CharString self)
+{
+ if (self != NULL) {
+ free(self->data);
+ free(self);
+ }
+}
diff --git a/source/base/CharString.h b/source/base/CharString.h
new file mode 100644
index 0000000..ff0e282
--- /dev/null
+++ b/source/base/CharString.h
@@ -0,0 +1,182 @@
+//
+// CharString.h - MrsWatson
+// Created by Nik Reiman on 1/2/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+
+#ifndef MrsWatson_CharString_h
+#define MrsWatson_CharString_h
+
+#include <stdlib.h>
+
+#include "base/LinkedList.h"
+#include "base/Types.h"
+
+static const size_t kCharStringLengthDefault = 256;
+static const size_t kCharStringLengthShort = 32;
+static const size_t kCharStringLengthLong = 8192;
+
+#define EMPTY_STRING ""
+#define DEFAULT_INDENT_SIZE 2
+#ifndef TERMINAL_LINE_LENGTH
+#define TERMINAL_LINE_LENGTH 80
+#endif
+
+#if WINDOWS
+#define strncasecmp _strnicmp
+#elif LINUX
+#include <strings.h>
+#endif
+
+typedef struct {
+ size_t capacity;
+ char *data;
+} CharStringMembers;
+typedef CharStringMembers *CharString;
+
+/**
+ * @return Create a new CharString instance
+ */
+CharString newCharString(void);
+
+/**
+ * @param length Number of characters
+ * @return Create a new CharString instance
+ */
+CharString newCharStringWithCapacity(size_t length);
+
+/**
+ * Create a new CharString from a C-String
+ * @param string C-String to use (copied to contents)
+ * @return New CharString instance
+ */
+CharString newCharStringWithCString(const char *string);
+
+/**
+ * Append another CharString to this instance, truncating if necessary. Appending
+ * a string to itself will result in undefined behavior.
+ * @param self
+ * @param string String to append
+ */
+void charStringAppend(CharString self, const CharString string);
+
+/**
+ * Append a C-String to this CharString. Appending a string to itself will result
+ * in undefined behavior.
+ * @param self
+ * @param string NULL-terminated string to append
+ */
+void charStringAppendCString(CharString self, const char *string);
+
+/**
+ * Copy the contents of another CharString to this one
+ * @param self
+ * @param string String to copy
+ */
+void charStringCopy(CharString self, const CharString string);
+
+/**
+ * Copy the contents of a C-String to this one
+ * @param self
+ * @param string NULL-terminated string to copy
+ */
+void charStringCopyCString(CharString self, const char *string);
+
+/**
+ * Clear a string's contents
+ * @param self
+ */
+void charStringClear(CharString self);
+
+/**
+ * @param self
+ * @return True if string is NULL or empty (ie, ""), false otherwise
+ */
+boolByte charStringIsEmpty(const CharString self);
+
+/**
+ * Test for string equality
+ * @param self
+ * @param string String to compare to
+ * @param caseInsensitive True for a case-insensitive comparison
+ * @return True if the strings are equal, false otherwise
+ */
+boolByte charStringIsEqualTo(const CharString self, const CharString string, boolByte caseInsensitive);
+
+/**
+ * Test for string equality
+ * @param self
+ * @param string NULL-terminated C-String to compare to
+ * @param caseInsensitive True for a case-insensitive comparison
+ * @return True if the strings are equal, false otherwise
+ */
+boolByte charStringIsEqualToCString(const CharString self, const char *string, boolByte caseInsensitive);
+
+/**
+ * Test if a given character in this string is a letter. This function does not
+ * do any bounds checking, and "letter" means an ASCII character.
+ * @param self
+ * @param index String index. If beyond the bounds of the string, undefined
+ * behavior will result.
+ * @return True if an ASCII character
+ */
+boolByte charStringIsLetter(const CharString self, const size_t index);
+
+/**
+ * Test if a given character in this string is a number. This function does not
+ * do any bounds checking.
+ * @param self
+ * @param index String index. If beyond the bounds of the string, undefined
+ * behavior will result.
+ * @return True is character is a number
+ */
+boolByte charStringIsNumber(const CharString self, const size_t index);
+
+/**
+ * Break a string up into a list of strings based on a delimeter character. The
+ * list of strings do not include the delimiter character itself.
+ * @param self
+ * @param delimiter Delimiter character (cannot be NULL)
+ * @return List of strings (may be an empty list if the delimiter was not found),
+ * or NULL if invalid input given.
+ */
+LinkedList charStringSplit(const CharString self, const char delimiter);
+
+/**
+ * Wrap a string to fix nicely within the width of a terminal window. This
+ * function is a bit inefficient, and should not be called in performance
+ * crucial code areas.
+ * @param self
+ * @param indentSize Initial indenting size to use
+ * @return The copy of the string line wrapped to fit in a terminal window
+ */
+CharString charStringWrap(const CharString self, unsigned int indentSize);
+
+/**
+ * Free a CharStar and its contents
+ * @param self
+ */
+void freeCharString(CharString self);
+
+#endif
diff --git a/source/base/Endian.c b/source/base/Endian.c
new file mode 100644
index 0000000..5eb3c81
--- /dev/null
+++ b/source/base/Endian.c
@@ -0,0 +1,93 @@
+//
+// Endian.c - MrsWatson
+// Created by Nik Reiman on 10 Dec 14.
+// Copyright (c) 2014 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include "base/Endian.h"
+#include "base/PlatformInfo.h"
+
+unsigned short flipShortEndian(const unsigned short value)
+{
+ return (value << 8) | (value >> 8);
+}
+
+unsigned short convertBigEndianShortToPlatform(const unsigned short value)
+{
+ if (platformInfoIsLittleEndian()) {
+ return (value << 8) | (value >> 8);
+ } else {
+ return value;
+ }
+}
+
+unsigned int convertBigEndianIntToPlatform(const unsigned int value)
+{
+ if (platformInfoIsLittleEndian()) {
+ return (value << 24) | ((value << 8) & 0x00ff0000) | ((value >> 8) & 0x0000ff00) | (value >> 24);
+ } else {
+ return value;
+ }
+}
+
+unsigned int convertLittleEndianIntToPlatform(const unsigned int value)
+{
+ if (!platformInfoIsLittleEndian()) {
+ return (value << 24) | ((value << 8) & 0x00ff0000) | ((value >> 8) & 0x0000ff00) | (value >> 24);
+ } else {
+ return value;
+ }
+}
+
+unsigned short convertByteArrayToUnsignedShort(const byte *value)
+{
+ if (platformInfoIsLittleEndian()) {
+ return ((value[1] << 8) & 0x0000ff00) | value[0];
+ } else {
+ return ((value[0] << 8) & 0x0000ff00) | value[1];
+ }
+}
+
+unsigned int convertByteArrayToUnsignedInt(const byte *value)
+{
+ if (platformInfoIsLittleEndian()) {
+ return ((value[3] << 24) | ((value[2] << 16) & 0x00ff0000) |
+ ((value[1] << 8) & 0x0000ff00) | value[0]);
+ } else {
+ return ((value[0] << 24) | ((value[1] << 16) & 0x00ff0000) |
+ ((value[2] << 8) & 0x0000ff00) | value[0]);
+ }
+}
+
+float convertBigEndianFloatToPlatform(const float value)
+{
+ float result = 0.0f;
+ byte *floatToConvert = (byte *)&value;
+ byte *floatResult = (byte *)&result;
+ floatResult[0] = floatToConvert[3];
+ floatResult[1] = floatToConvert[2];
+ floatResult[2] = floatToConvert[1];
+ floatResult[3] = floatToConvert[0];
+ return result;
+}
diff --git a/source/base/Endian.h b/source/base/Endian.h
new file mode 100644
index 0000000..27be1c7
--- /dev/null
+++ b/source/base/Endian.h
@@ -0,0 +1,89 @@
+//
+// Endian.h - MrsWatson
+// Created by Nik Reiman on 10 Dec 14.
+// Copyright (c) 2014 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+
+#ifndef MrsWatson_Endian_h
+#define MrsWatson_Endian_h
+
+#include "base/Types.h"
+
+/**
+ * Flip bytes for a short value. This does not take into account the host's
+ * endian-ness.
+ * @param value Short integer
+ * @return Flipped version of short integer
+ */
+unsigned short flipShortEndian(const unsigned short value);
+
+/**
+ * Convert a big endian short integer to the platform's native endian-ness.
+ * @param value Short integer
+ * @return Flipped version of short integer. If the host is big endian, the same
+ * value will be returned.
+ */
+unsigned short convertBigEndianShortToPlatform(const unsigned short value);
+
+/**
+ * Convert a big endian integer to the platform's native endian-ness.
+ * @param value Integer
+ * @return Flipped version of integer. If the host is big endian, the same value
+ * will be returned.
+ */
+unsigned int convertBigEndianIntToPlatform(const unsigned int value);
+
+/**
+ * Convert a little endian short integer to the platform's native endian-ness.
+ * @param value Short integer
+ * @return Flipped version of short integer. If the host is little endian, the
+ * same value will be returned.
+ */
+unsigned int convertLittleEndianIntToPlatform(const unsigned int value);
+
+/**
+ * Convert a big endian floating-point value to the platform's native endian-ness.
+ * @param value Floating-point number
+ * @return Flipped version of float. If the host is big endian, the same value
+ * will be returned.
+ */
+float convertBigEndianFloatToPlatform(const float value);
+
+/**
+ * Convert raw bytes to an unsigned short value, taking into account the host's
+ * endian-ness.
+ * @param value A buffer which holds at least two bytes
+ * @return Unsigned short integer
+ */
+unsigned short convertByteArrayToUnsignedShort(const byte *value);
+
+/**
+ * Convert raw bytes to an unsigned int value, taking into account the host's
+ * endian-ness.
+ * @param value A buffer which holds at least four bytes
+ * @return Unsigned short integer
+ */
+unsigned int convertByteArrayToUnsignedInt(const byte *value);
+
+#endif
diff --git a/source/base/File.c b/source/base/File.c
new file mode 100644
index 0000000..f420eb6
--- /dev/null
+++ b/source/base/File.c
@@ -0,0 +1,922 @@
+//
+// File.c - MrsWatson
+// Created by Nik Reiman on 09 Dec 12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+
+// Must be declared before stdlib, shouldn't have any effect on Windows builds
+#define _XOPEN_SOURCE 700
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include "base/File.h"
+#include "logging/EventLogger.h"
+
+#if WINDOWS
+#include <Windows.h>
+#include <Shellapi.h>
+#elif UNIX
+#if LINUX
+#include <errno.h>
+#elif MACOSX
+#include <mach-o/dyld.h>
+#endif
+
+#include <dirent.h>
+#include <ftw.h>
+#include <unistd.h>
+#endif
+
+static const int kFileNameStringLength = 1024;
+static const int kFileMaxRecursionDepth = 32;
+static const char *kFileNameInvalidCharacters = "*:?<>|";
+
+static boolByte _isAbsolutePath(const CharString path)
+{
+#if WINDOWS
+
+ if (path->capacity > 3) {
+ // Check for strings which start with a drive letter, ie C:\file
+ if (path->data[1] == ':' && path->data[2] == PATH_DELIMITER) {
+ return true;
+ }
+ // Check for network paths, ie \\SERVER\file
+ else if (path->data[0] == PATH_DELIMITER && path->data[1] == PATH_DELIMITER) {
+ return true;
+ }
+ }
+
+#else
+
+ if (path->capacity > 1 && path->data[0] == PATH_DELIMITER) {
+ return true;
+ }
+
+#endif
+ return false;
+}
+
+static CharString _convertRelativePathToAbsolute(const CharString path)
+{
+ CharString result = newCharStringWithCapacity(kFileNameStringLength);
+ CharString currentDirectory = fileGetCurrentDirectory();
+ snprintf(result->data, result->capacity, "%s%c%s", currentDirectory->data, PATH_DELIMITER, path->data);
+ freeCharString(currentDirectory);
+ return result;
+}
+
+static CharString _buildAbsolutePath(const CharString parentDir, const CharString filename)
+{
+ CharString result = NULL;
+ CharString parentDirAbsPath = NULL;
+
+ if (parentDir == NULL || charStringIsEmpty(parentDir)) {
+ logWarn("Attempt to build absolute path with empty directory");
+ return result;
+ } else if (filename == NULL || charStringIsEmpty(filename)) {
+ logWarn("Attempt to build absolute path with empty file");
+ return result;
+ } else if (_isAbsolutePath(filename)) {
+ result = newCharStringWithCapacity(kFileNameStringLength);
+ charStringCopy(result, filename);
+ return result;
+ }
+
+ if (_isAbsolutePath(parentDir)) {
+ parentDirAbsPath = newCharStringWithCapacity(kFileNameStringLength);
+ charStringCopy(parentDirAbsPath, parentDir);
+ } else {
+ parentDirAbsPath = _convertRelativePathToAbsolute(parentDir);
+ }
+
+ result = newCharStringWithCapacity(kFileNameStringLength);
+ snprintf(result->data, result->capacity, "%s%c%s", parentDirAbsPath->data, PATH_DELIMITER, filename->data);
+ freeCharString(parentDirAbsPath);
+ return result;
+}
+
+static boolByte _isDirectory(const CharString path)
+{
+ boolByte result = false;
+
+#if UNIX
+ struct stat buffer;
+
+ if (stat(path->data, &buffer) == 0) {
+ result = (boolByte)S_ISDIR(buffer.st_mode);
+ }
+
+#elif WINDOWS
+ DWORD fileAttributes = GetFileAttributesA((LPCSTR)path->data);
+
+ if (fileAttributes != INVALID_FILE_ATTRIBUTES) {
+ result = (boolByte)(fileAttributes & FILE_ATTRIBUTE_DIRECTORY);
+ }
+
+#endif
+
+ return result;
+}
+
+static boolByte _pathContainsInvalidChars(const CharString path)
+{
+ size_t i = 0;
+#if WINDOWS
+
+ // The colon is not allowed in pathnames (even on Windows), however it is
+ // present in absolute pathnames for the drive letter. Thus we must skip
+ // the first 3 characters of the path when dealing with absolute paths on
+ // this platform.
+ if (_isAbsolutePath(path)) {
+ i = 3;
+ }
+
+#endif
+
+ if (path != NULL) {
+ for (i = 0; i < strlen(kFileNameInvalidCharacters); ++i) {
+ if (strchr(path->data, kFileNameInvalidCharacters[i]) != NULL) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+File newFile(void)
+{
+ File result = (File)malloc(sizeof(FileMembers));
+ result->absolutePath = newCharStringWithCapacity(kFileNameStringLength);
+ result->fileType = kFileTypeInvalid;
+ result->_fileHandle = NULL;
+ result->_openMode = kFileOpenModeClosed;
+ return result;
+}
+
+File newFileWithPath(const CharString path)
+{
+ File result = newFile();
+ CharString currentDirectory = NULL;
+ CharString absolutePath = NULL;
+
+ if (path != NULL && !charStringIsEmpty(path)) {
+ if (_isAbsolutePath(path)) {
+ charStringCopy(result->absolutePath, path);
+ } else if (_pathContainsInvalidChars(path)) {
+ logWarn("Could not create file/directory with name '%s'", path->data);
+ freeFile(result);
+ return NULL;
+ } else {
+ currentDirectory = fileGetCurrentDirectory();
+
+ if (currentDirectory == NULL) {
+ logWarn("Could not create file relative to current directory");
+ freeFile(result);
+ return NULL;
+ }
+
+ absolutePath = _buildAbsolutePath(currentDirectory, path);
+ charStringCopy(result->absolutePath, absolutePath);
+ freeCharString(currentDirectory);
+ freeCharString(absolutePath);
+ }
+
+ if (fileExists(result)) {
+ result->fileType = _isDirectory(result->absolutePath) ? kFileTypeDirectory : kFileTypeFile;
+ }
+ }
+
+ return result;
+}
+
+File newFileWithPathCString(const char *path)
+{
+ File result = NULL;
+
+ if (path != NULL) {
+ CharString pathString = newCharStringWithCString(path);
+ result = newFileWithPath(pathString);
+ freeCharString(pathString);
+ }
+
+ return result;
+}
+
+File newFileWithParent(const File parent, const CharString path)
+{
+ File result = NULL;
+ CharString absolutePath = NULL;
+
+ if (parent == NULL) {
+ logWarn("Cannot create file/directory with null parent");
+ return NULL;
+ } else if (!fileExists(parent)) {
+ logWarn("Cannot create file/directory under non-existent parent");
+ return NULL;
+ } else if (!_isDirectory(parent->absolutePath)) {
+ logWarn("Cannot create file/directory under non-directory parent '%s'", parent->absolutePath->data);
+ return NULL;
+ } else if (path == NULL || charStringIsEmpty(path)) {
+ logWarn("Cannot create empty file/directory name with parent");
+ return NULL;
+ } else if (_pathContainsInvalidChars(path)) {
+ logWarn("Could not create file/directory with name '%s'", path->data);
+ freeFile(result);
+ return NULL;
+ } else if (_isAbsolutePath(path)) {
+ logWarn("Cannot create file '%s' with absolute directory under a parent", path->data);
+ freeFile(result);
+ return NULL;
+ }
+
+ result = newFile();
+ absolutePath = _buildAbsolutePath(parent->absolutePath, path);
+ charStringCopy(result->absolutePath, absolutePath);
+ freeCharString(absolutePath);
+
+ if (fileExists(result)) {
+ result->fileType = _isDirectory(result->absolutePath) ? kFileTypeDirectory : kFileTypeFile;
+ }
+
+ return result;
+}
+
+boolByte fileExists(File self)
+{
+#if WINDOWS
+ // Visual Studio's compiler is not C99 compliant, so variable declarations
+ // need to be at the top.
+ unsigned long fileAttributes;
+#endif
+
+ // Normally, we don't do paranoid sanity checks for self != NULL, but in this
+ // case it's useful since the old file API had a different calling convention
+ // which supported calling fileExists() with NULL.
+ if (self == NULL || self->absolutePath == NULL) {
+ return false;
+ }
+
+#if UNIX
+ struct stat fileStat;
+ boolByte result = (boolByte)(stat(self->absolutePath->data, &fileStat) == 0);
+ return result;
+
+#elif WINDOWS
+ fileAttributes = GetFileAttributesA((LPCSTR)self->absolutePath->data);
+
+ if (fileAttributes == INVALID_FILE_ATTRIBUTES) {
+ return false;
+ }
+
+ return true;
+#else
+ return false;
+#endif
+}
+
+boolByte fileCreate(File self, const FileType fileType)
+{
+ if (fileExists(self)) {
+ return false;
+ } else if (charStringIsEmpty(self->absolutePath)) {
+ logWarn("Attempt to create file/directory without a path");
+ return false;
+ }
+
+ switch (fileType) {
+ case kFileTypeFile:
+ self->_fileHandle = fopen(self->absolutePath->data, "wb");
+
+ if (self->_fileHandle != NULL) {
+ self->fileType = kFileTypeFile;
+ self->_openMode = kFileOpenModeWrite;
+ return true;
+ }
+
+ break;
+
+ case kFileTypeDirectory:
+#if UNIX
+ if (mkdir(self->absolutePath->data, 0755) == 0) {
+#elif WINDOWS
+
+ if (CreateDirectoryA(self->absolutePath->data, NULL)) {
+#endif
+ self->fileType = kFileTypeDirectory;
+ return true;
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ return false;
+}
+
+static File _copyFileToDirectory(File self, const File destination) {
+ File result = NULL;
+ CharString basename = NULL;
+ void *selfContents = NULL;
+ size_t selfSize = 0;
+
+ // Close and re-open file to make sure that we start reading at the beginning
+ fileClose(self);
+ self->_fileHandle = fopen(self->absolutePath->data, "rb");
+
+ if (self->_fileHandle == NULL) {
+ logError("Could not re-open file for reading during copy");
+ return NULL;
+ }
+
+ basename = fileGetBasename(self);
+
+ if (basename == NULL) {
+ logError("Could not get basename during copy");
+ } else {
+ result = newFileWithParent(destination, basename);
+ freeCharString(basename);
+
+ if (result == NULL) {
+ logError("Could not create destination file object");
+ return NULL;
+ }
+
+ if (!fileCreate(result, kFileTypeFile)) {
+ logError("Could not create destination file");
+ freeFile(result);
+ return NULL;
+ }
+
+ selfSize = fileGetSize(self);
+
+ if (selfSize == 0) {
+ // If the source file is empty, then creating the result file is good
+ // enough and we can return here.
+ logDebug("Source file during copy is 0 bytes");
+ return result;
+ }
+
+ selfContents = fileReadBytes(self, selfSize);
+
+ if (selfContents == NULL) {
+ logError("Could not read source file during copy");
+ freeFile(result);
+ return NULL;
+ }
+
+ if (!fileWriteBytes(result, selfContents, selfSize)) {
+ logError("Could not copy source file");
+ freeFile(result);
+ return NULL;
+ }
+ }
+
+ return result;
+}
+
+static File _copyDirectoryToDirectory(File self, const File destination) {
+ File result = NULL;
+ // Get the basename first, because if it fails then there's no point in doing
+ // the actual copy.
+ CharString basename = fileGetBasename(self);
+#if WINDOWS
+ SHFILEOPSTRUCT fileOperation;
+#endif
+
+ if (basename == NULL) {
+ logError("Could not get basename from directory during copy");
+ return NULL;
+ }
+
+#if UNIX
+ // Using nftw() is a real pain here, because the recursive callback will start
+ // at the top of the tree and work back up, meaning that for each file we'd
+ // need to recursively mkdir the basename, etc.
+ // So this is a bit lazy but actually more foolproof
+ CharString copyCommand = newCharString();
+ snprintf(copyCommand->data, copyCommand->capacity, "/bin/cp -r \"%s\" \"%s\"",
+ self->absolutePath->data, destination->absolutePath->data);
+ int systemResult = system(copyCommand->data);
+ freeCharString(copyCommand);
+
+ if (WEXITSTATUS(systemResult) == 0) {
+ result = newFileWithParent(destination, basename);
+
+ if (result == NULL) {
+ logError("Copied '%s' to '%s', but could not create a File object for the result",
+ self->absolutePath->data, destination->absolutePath->data);
+ freeCharString(basename);
+ return NULL;
+ }
+ } else {
+ logError("Could not copy directory '%s' to '%s'",
+ self->absolutePath->data, destination->absolutePath->data);
+ }
+
+#elif WINDOWS
+ memset(&fileOperation, 0, sizeof(fileOperation));
+ fileOperation.wFunc = FO_COPY;
+ fileOperation.pFrom = self->absolutePath->data;
+ fileOperation.pTo = destination->absolutePath->data;
+ fileOperation.fFlags = FOF_NO_UI;
+
+ if (SHFileOperationA(&fileOperation) == 0) {
+ result = newFileWithParent(destination, basename);
+ }
+
+#endif
+
+ freeCharString(basename);
+ return result;
+}
+
+File fileCopyTo(File self, const File destination) {
+ File result = NULL;
+
+ if (destination == NULL || !fileExists(destination)) {
+ logError("Attempt to copy file/directory to invalid destination");
+ return NULL;
+ } else if (!_isDirectory(destination->absolutePath)) {
+ logError("Attempt to copy file/directory to non-directory destination");
+ return NULL;
+ } else if (!fileExists(self)) {
+ logError("Attempt to copy non-existent file");
+ return NULL;
+ }
+
+ switch (self->fileType) {
+ case kFileTypeFile:
+ result = _copyFileToDirectory(self, destination);
+ break;
+
+ case kFileTypeDirectory:
+ result = _copyDirectoryToDirectory(self, destination);
+ break;
+
+ default:
+ logError("Attempt to copy invalid file type");
+ break;
+ }
+
+ return result;
+}
+
+#if UNIX
+static int _removeCallback(const char *path, const struct stat * fileState, int typeflag, struct FTW * ftwBuffer) {
+ int result = remove(path);
+
+ if (result != 0) {
+ logWarn("Could not remove '%s'", path);
+ }
+
+ return result;
+}
+#endif
+
+boolByte fileRemove(File self) {
+ boolByte result = false;
+#if WINDOWS
+ SHFILEOPSTRUCTA fileOperation = {0};
+#endif
+
+ if (fileExists(self)) {
+ switch (self->fileType) {
+ case kFileTypeFile:
+ // Yes, this seems a bit silly, but otherwise we threaten to leak resources
+ fileClose(self);
+ result = (boolByte)(remove(self->absolutePath->data) == 0);
+ break;
+
+ case kFileTypeDirectory:
+#if UNIX
+ result = (boolByte)(nftw(self->absolutePath->data, _removeCallback, kFileMaxRecursionDepth, FTW_DEPTH | FTW_PHYS) == 0);
+#elif WINDOWS
+ memset(&fileOperation, 0, sizeof(fileOperation));
+ fileOperation.wFunc = FO_DELETE;
+ fileOperation.pFrom = self->absolutePath->data;
+ fileOperation.pTo = NULL;
+ fileOperation.fFlags = FOF_NO_UI | FOF_NOCONFIRMATION | FOF_SILENT;
+ result = (SHFileOperationA(&fileOperation) == 0);
+#endif
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (result) {
+ self->fileType = kFileTypeInvalid;
+ }
+
+ return result;
+}
+
+LinkedList fileListDirectory(File self) {
+ LinkedList items = newLinkedList();
+ CharString path;
+ File file;
+
+#if UNIX
+ DIR *directoryPtr = opendir(self->absolutePath->data);
+
+ if (directoryPtr == NULL) {
+ freeLinkedList(items);
+ return 0;
+ }
+
+ struct dirent *entry;
+
+ while ((entry = readdir(directoryPtr)) != NULL) {
+ if (entry->d_name[0] != '.') {
+ path = newCharStringWithCString(entry->d_name);
+ file = newFileWithParent(self, path);
+ linkedListAppend(items, file);
+ freeCharString(path);
+ }
+ }
+
+ closedir(directoryPtr);
+
+#elif WINDOWS
+ WIN32_FIND_DATAA findData;
+ HANDLE findHandle;
+ CharString searchString = newCharStringWithCapacity(kFileNameStringLength);
+
+ snprintf(searchString->data, searchString->capacity, "%s\\*", self->absolutePath->data);
+ findHandle = FindFirstFileA((LPCSTR)(searchString->data), &findData);
+ freeCharString(searchString);
+
+ if (findHandle == INVALID_HANDLE_VALUE) {
+ freeLinkedList(items);
+ return 0;
+ }
+
+ do {
+ if (findData.cFileName[0] != '.') {
+ path = newCharStringWithCString(findData.cFileName);
+ file = newFileWithParent(self, path);
+ linkedListAppend(items, file);
+ freeCharString(path);
+ }
+ } while (FindNextFileA(findHandle, &findData) != 0);
+
+ FindClose(findHandle);
+
+#else
+ logUnsupportedFeature("List directory contents");
+#endif
+
+ return items;
+}
+
+size_t fileGetSize(File self) {
+ size_t result = 0;
+
+#if UNIX
+ struct stat fileStat;
+
+ if (self->absolutePath == NULL) {
+ return 0;
+ }
+
+ if (stat(self->absolutePath->data, &fileStat) == 0) {
+ if (S_ISREG(fileStat.st_mode)) {
+ // Yes, this will result in a loss of precision, but both freah and fwrite
+ // take a size_t argument, which is what this function is mostly used for.
+ result = (size_t)fileStat.st_size;
+ }
+ }
+
+#elif WINDOWS
+ HANDLE handle = CreateFileA(self->absolutePath->data, GENERIC_READ, 0, NULL,
+ OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+
+ if (handle != INVALID_HANDLE_VALUE) {
+ result = GetFileSize(handle, NULL);
+ CloseHandle(handle);
+ }
+
+#else
+ logUnsupportedFeature("Get file size");
+#endif
+
+ return result;
+}
+
+CharString fileReadContents(File self) {
+ CharString result = NULL;
+ size_t fileSize = 0;
+ size_t itemsRead = 0;
+
+ if (self->fileType != kFileTypeFile) {
+ logError("Attempt to read contents from non-file object '%s'", self->absolutePath->data);
+ return NULL;
+ }
+
+ // Windows has problems opening files multiple times (which is done internally in
+ // fileGetSize() via CreateFile), so we must close the file first, calculate size,
+ // and then reopen it for reading.
+ fileClose(self);
+ fileSize = (size_t)fileGetSize(self);
+ fileClose(self);
+
+ if (self->_fileHandle == NULL) {
+ self->_fileHandle = fopen(self->absolutePath->data, "rb");
+
+ if (self->_fileHandle == NULL) {
+ logError("Could not open '%s' for reading", self->absolutePath->data);
+ return NULL;
+ } else {
+ self->_openMode = kFileOpenModeRead;
+ }
+ }
+
+ if (fileSize > 0) {
+ result = newCharStringWithCapacity(fileSize + 1);
+ itemsRead = fread(result->data, 1, fileSize, self->_fileHandle);
+
+ if (itemsRead != fileSize) {
+ logError("Expected to read %d items, read %d items instead", fileSize, itemsRead);
+ }
+ }
+
+ return result;
+}
+
+LinkedList fileReadLines(File self) {
+ LinkedList result = NULL;
+ CharString line = NULL;
+ boolByte done = false;
+ char *newline = NULL;
+
+ if (self->fileType != kFileTypeFile) {
+ logError("Attempt to read contents from non-file object '%s'", self->absolutePath->data);
+ return NULL;
+ }
+
+ if (self->_openMode != kFileOpenModeRead && self->_fileHandle != NULL) {
+ fileClose(self);
+ }
+
+ if (self->_fileHandle == NULL) {
+ self->_fileHandle = fopen(self->absolutePath->data, "rb");
+
+ if (self->_fileHandle == NULL) {
+ logError("Could not open '%s' for reading", self->absolutePath->data);
+ return NULL;
+ } else {
+ self->_openMode = kFileOpenModeRead;
+ }
+ }
+
+ result = newLinkedList();
+
+ while (!done) {
+ line = newCharString();
+
+ if (fgets(line->data, (int)line->capacity, self->_fileHandle) == NULL) {
+ freeCharString(line);
+ done = true;
+ } else {
+ newline = strrchr(line->data, '\n');
+
+ if (newline != NULL) {
+ *newline = '\0';
+ }
+
+ // Also trim these characters, in case the file has Windows-style newlines
+ newline = strrchr(line->data, '\r');
+
+ if (newline != NULL) {
+ *newline = '\0';
+ }
+
+ linkedListAppend(result, line);
+ }
+ }
+
+ return result;
+}
+
+void *fileReadBytes(File self, size_t numBytes) {
+ void *result = NULL;
+ size_t itemsRead = 0;
+
+ if (numBytes == 0) {
+ // Here we log an error (instead of an info message, as is done with the
+ // same type of error during writes) because the caller is probably
+ // expecting a non-NULL object back.
+ logError("Attempt to read 0 bytes from file");
+ return NULL;
+ }
+
+ if (self->fileType != kFileTypeFile) {
+ logError("Attempt to read bytes from non-file object '%s'", self->absolutePath->data);
+ return NULL;
+ }
+
+ if (self->_openMode != kFileOpenModeRead && self->_fileHandle != NULL) {
+ fileClose(self);
+ }
+
+ if (self->_fileHandle == NULL) {
+ self->_fileHandle = fopen(self->absolutePath->data, "rb");
+
+ if (self->_fileHandle == NULL) {
+ logError("Could not open '%s' for reading", self->absolutePath->data);
+ return NULL;
+ } else {
+ self->_openMode = kFileOpenModeRead;
+ }
+ }
+
+ if (numBytes > 0) {
+ result = malloc(numBytes + 1);
+ memset(result, 0, numBytes + 1);
+ itemsRead = fread(result, 1, numBytes, self->_fileHandle);
+
+ if (itemsRead != numBytes) {
+ logError("Expected to read %d items, read %d items instead", numBytes, itemsRead);
+ }
+ }
+
+ return result;
+}
+
+boolByte fileWrite(File self, const CharString data) {
+ return fileWriteBytes(self, data->data, strlen(data->data));
+}
+
+boolByte fileWriteBytes(File self, const void *data, size_t numBytes) {
+ size_t itemsWritten = 0;
+
+ if (numBytes == 0) {
+ // Here we only log an info message, as this isn't such a critical error,
+ // but it is rather weird.
+ logInfo("Attempt to write 0 bytes to file");
+ return false;
+ }
+
+ if (!fileExists(self)) {
+ logError("Attempt to write to non-existent file");
+ return false;
+ }
+
+ if (self->_openMode != kFileOpenModeWrite && self->_fileHandle != NULL) {
+ fileClose(self);
+ }
+
+ if (self->_fileHandle == NULL) {
+ self->_fileHandle = fopen(self->absolutePath->data, "wb");
+
+ if (self->_fileHandle == NULL) {
+ logError("Could not open '%s' for writing", self->absolutePath->data);
+ return false;
+ } else {
+ self->_openMode = kFileOpenModeWrite;
+ }
+ }
+
+ itemsWritten = fwrite(data, 1, numBytes, self->_fileHandle);
+ return (boolByte)(itemsWritten == numBytes);
+}
+
+CharString fileGetBasename(File self) {
+ CharString result = NULL;
+ char *lastDelimiter = NULL;
+
+ if (self->absolutePath == NULL || charStringIsEmpty(self->absolutePath)) {
+ return NULL;
+ }
+
+ lastDelimiter = strrchr(self->absolutePath->data, PATH_DELIMITER);
+
+ if (lastDelimiter == NULL) {
+ result = newCharStringWithCString(self->absolutePath->data);
+ } else {
+ result = newCharStringWithCString(lastDelimiter + 1);
+ }
+
+ return result;
+}
+
+File fileGetParent(File self) {
+ File result = NULL;
+ CharString path = NULL;
+ char *lastDelimiter = NULL;
+
+ if (self->absolutePath == NULL || charStringIsEmpty(self->absolutePath)) {
+ return NULL;
+ }
+
+ lastDelimiter = strrchr(self->absolutePath->data, PATH_DELIMITER);
+
+ if (lastDelimiter == NULL) {
+ result = newFileWithPath(self->absolutePath);
+ } else {
+ path = newCharStringWithCapacity(kFileNameStringLength);
+ strncpy(path->data, self->absolutePath->data, lastDelimiter - self->absolutePath->data);
+ result = newFileWithPath(path);
+ freeCharString(path);
+ }
+
+ return result;
+}
+
+CharString fileGetExtension(File self) {
+ CharString basename = fileGetBasename(self);
+ CharString result = NULL;
+ char *dot = NULL;
+
+ if (basename == NULL || charStringIsEmpty(basename)) {
+ freeCharString(basename);
+ return NULL;
+ }
+
+ dot = strrchr(basename->data, '.');
+
+ if (dot != NULL) {
+ result = newCharStringWithCString(dot + 1);
+ }
+
+ freeCharString(basename);
+ return result;
+}
+
+CharString fileGetExecutablePath(void) {
+ CharString executablePath = newCharString();
+#if LINUX
+ ssize_t result = readlink("/proc/self/exe", executablePath->data, executablePath->capacity);
+
+ if (result < 0) {
+ logWarn("Could not find executable path, %s", stringForLastError(errno));
+ return NULL;
+ }
+
+#elif MACOSX
+ _NSGetExecutablePath(executablePath->data, (uint32_t *)&executablePath->capacity);
+#elif WINDOWS
+ GetModuleFileNameA(NULL, executablePath->data, (DWORD)executablePath->capacity);
+#endif
+ return executablePath;
+}
+
+CharString fileGetCurrentDirectory(void) {
+ CharString currentDirectory = newCharString();
+#if UNIX
+
+ if (getcwd(currentDirectory->data, currentDirectory->capacity) == NULL) {
+ logError("Could not get current working directory");
+ freeCharString(currentDirectory);
+ return NULL;
+ }
+
+#elif WINDOWS
+ GetCurrentDirectoryA((DWORD)currentDirectory->capacity, currentDirectory->data);
+#endif
+ return currentDirectory;
+}
+
+void fileClose(File self) {
+ if (self->_fileHandle != NULL && self->fileType == kFileTypeFile) {
+ fflush(self->_fileHandle);
+ fclose(self->_fileHandle);
+ self->_fileHandle = NULL;
+ self->_openMode = kFileOpenModeClosed;
+ }
+}
+
+void freeFile(File self) {
+ if (self) {
+ fileClose(self);
+ freeCharString(self->absolutePath);
+ free(self);
+ }
+}
diff --git a/source/base/File.h b/source/base/File.h
new file mode 100644
index 0000000..9c30924
--- /dev/null
+++ b/source/base/File.h
@@ -0,0 +1,262 @@
+//
+// File.h - MrsWatson
+// Created by Nik Reiman on 09 Dec 12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+
+#ifndef MrsWatson_File_h
+#define MrsWatson_File_h
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "base/CharString.h"
+#include "base/LinkedList.h"
+#include "base/Types.h"
+
+#if UNIX
+#define PATH_DELIMITER '/'
+#define ROOT_DIRECTORY "/"
+#elif WINDOWS
+#define PATH_DELIMITER '\\'
+#define ROOT_DIRECTORY "C:\\"
+#endif
+
+typedef enum {
+ kFileTypeFile,
+ kFileTypeDirectory,
+ kFileTypeInvalid
+} FileType;
+
+typedef enum {
+ kFileOpenModeClosed,
+ kFileOpenModeRead,
+ kFileOpenModeWrite,
+ kFileOpenModeInvalid
+} FileOpenMode;
+
+typedef struct {
+ CharString absolutePath;
+ FileType fileType;
+
+ /** Private */
+ FILE *_fileHandle;
+ /** Private */
+ FileOpenMode _openMode;
+} FileMembers;
+typedef FileMembers *File;
+
+/**
+ * @return New empty file object
+ */
+File newFile(void);
+
+/**
+ * Create a new file object which points to a given path. If something exists at
+ * the path, then this object will be initialized with the correct type.
+ * @param path Object path. If this is not an absolute path, it is assumed to
+ * be relative to the current directory.
+ * @return New file object
+ */
+File newFileWithPath(const CharString path);
+
+/**
+ * Create a new file object which points to a given path. If something exists at
+ * the path, then this object will be initialized with the correct type.
+ * @param path Object path. If this is not an absolute path, it is assumed to
+ * be relative to the current directory.
+ * @return New file object
+ */
+File newFileWithPathCString(const char *path);
+
+/**
+ * Create a new file object which points to a path under another directory. If
+ * something exists at the path, then this object will be initialized with the
+ * correct type.
+ * @param parent Directory which the object is located under. If parent does not
+ * exist or is not a directory, this will return NULL.
+ * @param path Object path, relative to the parent. This may not be an absolute
+ * path.
+ * @return New file object, or NULL if this object could not be created.
+ */
+File newFileWithParent(const File parent, const CharString path);
+
+/**
+ * Check to see if the path referenced by this object exists on disk.
+ * @param self
+ * @return True if the object exists on disk
+ */
+boolByte fileExists(File self);
+
+/**
+ * Create a file object on disk of the given type. This call will fail if an
+ * object already exists on the disk at the path pointed to by this file.
+ * @param self
+ * @param fileType Type of object to create
+ * @return True if the object was created
+ */
+boolByte fileCreate(File self, const FileType fileType);
+
+/**
+ * Copy a file object to a new location. If this file is a directory, it will
+ * be copied recursively. This call will fail if the destination does not exist.
+ * @param self
+ * @param destination Target destination to copy objects to.
+ * @return File object for copied object, or NULL if it could not be copied. The
+ * caller must free this object when finished with it.
+ */
+File fileCopyTo(File self, const File destination);
+
+/**
+ * Remove the file from disk. If this object is a directory, then it will be
+ * removed recursively.
+ * Note: On Windows, you must make sure that all files inside of this directory
+ * are closed, otherwise the operation will fail.
+ * @param self
+ * @return True if the object could be removed
+ */
+boolByte fileRemove(File self);
+
+/**
+ * List the contents of a directory, non-recursively. Special entries such as
+ * "." and ".." are not included in the listing, nor are hidden dotfiles.
+ * @param self
+ * @return A linked list of File objects, or NULL on error. The caller must
+ * release this memory using freeLinkedListAndItems.
+ */
+LinkedList fileListDirectory(File self);
+
+/**
+ * Return the size of a file in bytes.
+ * @param self
+ * @return Number of bytes in the file, or 0 if this object does not exist or
+ * is not a file.
+ */
+size_t fileGetSize(File self);
+
+/**
+ * Read the contents of an entire file into a string. If the file had previously
+ * been opened for writing, then it will be flushed, closed, and reopened for
+ * reading.
+ * @param self
+ * @return CharString containing file contents, or NULL if an error occurred.
+ */
+CharString fileReadContents(File self);
+
+/**
+ * Read the contents of an entire file line-by-line. The lines are returned in
+ * a linked list, which the caller must free when finished (and should use the
+ * freeLinkedListAndItems() method with freeCharString as the second argument).
+ * @param self
+ * @return LinkedList containing a CharString for each line, or NULL if an
+ * error occurred. Note that the newline character is removed from the lines.
+ */
+LinkedList fileReadLines(File self);
+
+/**
+ * Read part of a binary file into a raw byte array. If end of file is reached,
+ * then an array of numBytes is still delivered with the extra bytes initialized
+ * to 0. This is most useful when reading raw PCM data from disk. If the file
+ * had previously been opened for writing, then it will be flushed, closed, and
+ * reopened for reading.
+ * @param self
+ * @param numBytes Number of bytes to deliver, at most. If this is greater than
+ * the size of the actual file, then the entire file will be read. If this is
+ * zero, then NULL will be returned.
+ * @return An initialized array of numBytes bytes with the data, or NULL if an
+ * error occurred.
+ */
+void *fileReadBytes(File self, size_t numBytes);
+
+/**
+ * Write a string to file. The first time this function is called, the file will
+ * be opened for write mode, truncating any data present there if the file
+ * already exists. File buffers are not flushed until fileClose() is called.
+ * @param self
+ * @param data String to write
+ * @return True if the data was written
+ */
+boolByte fileWrite(File self, const CharString data);
+
+/**
+ * Write a binary byte array to disk. The first time this function is called,
+ * the file will be opened for write mode, truncating any data present there if
+ * the file already exists. File buffers are not flushed until fileClose() is
+ * called.
+ * @param self
+ * @param data Binary data to write
+ * @param numBytes Number of bytes to write
+ * @return True if the data could be written
+ */
+boolByte fileWriteBytes(File self, const void *data, size_t numBytes);
+
+/**
+ * Get the file basename, for example "/foo/bar" would return "bar" (regardless
+ * of whether "bar" is a file or directory).
+ * @param self
+ * @return CharString containing basename. The caller must free this memory when
+ * finished with it.
+ */
+CharString fileGetBasename(File self);
+
+/**
+ * Get a file's parent directory.
+ * @param self
+ * @return File representing the parent directory. The caller must free this
+ * object when finished with it.
+ */
+File fileGetParent(File self);
+
+/**
+ * Return a pointer to the file's extension.
+ * @param self
+ * @return Pointer to file extension, or NULL if the file has no extension or
+ * an error occurred.
+ */
+CharString fileGetExtension(File self);
+
+/**
+ * Return the path to the current running executable.
+ * @return Absolute path of the current executable
+ */
+CharString fileGetExecutablePath(void);
+
+/**
+ * Get the current working directory.
+ * @return Current working directory
+ */
+CharString fileGetCurrentDirectory(void);
+
+/**
+ * Close a file and flush its buffers to disk.
+ * @param self
+ */
+void fileClose(File self);
+
+/**
+ * Free a file object and any associated resources
+ * @param self
+ */
+void freeFile(File self);
+
+#endif
diff --git a/source/base/LinkedList.c b/source/base/LinkedList.c
new file mode 100644
index 0000000..4eec9c3
--- /dev/null
+++ b/source/base/LinkedList.c
@@ -0,0 +1,157 @@
+//
+// LinkedList.c - MrsWatson
+// Created by Nik Reiman on 1/3/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "base/LinkedList.h"
+#include "base/Types.h"
+
+LinkedList newLinkedList(void)
+{
+ LinkedList list = malloc(sizeof(LinkedListMembers));
+
+ list->item = NULL;
+ list->nextItem = NULL;
+ list->_numItems = 0;
+
+ return list;
+}
+
+void linkedListAppend(LinkedList self, void *item)
+{
+ LinkedListIterator iterator = self;
+ LinkedListIterator headNode;
+ LinkedList nextItem;
+
+ if (self == NULL || item == NULL) {
+ return;
+ }
+
+ // First item in the list
+ if (iterator->item == NULL) {
+ iterator->item = item;
+ iterator->_numItems = 1;
+ return;
+ }
+
+ headNode = self;
+
+ while (true) {
+ if (iterator->nextItem == NULL) {
+ nextItem = newLinkedList();
+ nextItem->item = item;
+ iterator->nextItem = nextItem;
+ headNode->_numItems++;
+ break;
+ } else {
+ iterator = (LinkedListIterator)(iterator->nextItem);
+ }
+ }
+}
+
+int linkedListLength(LinkedList self)
+{
+ return self != NULL ? self->_numItems : 0;
+}
+
+void **linkedListToArray(LinkedList self)
+{
+ LinkedListIterator iterator = self;
+ void **array;
+ int i = 0;
+
+ if (self == NULL || linkedListLength(self) == 0) {
+ return NULL;
+ }
+
+ array = (void **)malloc(sizeof(void *) * (linkedListLength(self) + 1));
+
+ while (iterator != NULL) {
+ if (iterator->item != NULL) {
+ array[i++] = iterator->item;
+ }
+
+ iterator = iterator->nextItem;
+ }
+
+ array[i] = NULL;
+ return array;
+}
+
+void linkedListForeach(LinkedList self, LinkedListForeachFunc foreachFunc, void *userData)
+{
+ LinkedListIterator iterator = self;
+
+ while (iterator != NULL) {
+ if (iterator->item != NULL) {
+ foreachFunc(iterator->item, userData);
+ }
+
+ iterator = iterator->nextItem;
+ }
+}
+
+void freeLinkedList(LinkedList self)
+{
+ LinkedListIterator iterator = self;
+
+ while (iterator != NULL) {
+ if (iterator->nextItem == NULL) {
+ free(iterator);
+ break;
+ } else {
+ LinkedList current = iterator;
+ iterator = iterator->nextItem;
+ free(current);
+ }
+ }
+}
+
+void freeLinkedListAndItems(LinkedList self, LinkedListFreeItemFunc freeItem)
+{
+ LinkedListIterator iterator = self;
+ LinkedList current;
+
+ if (iterator->item == NULL) {
+ free(iterator);
+ return;
+ }
+
+ while (true) {
+ if (iterator->nextItem == NULL) {
+ freeItem(iterator->item);
+ free(iterator);
+ break;
+ } else {
+ freeItem(iterator->item);
+ current = iterator;
+ iterator = (LinkedListIterator)(iterator->nextItem);
+ free(current);
+ }
+ }
+}
diff --git a/source/base/LinkedList.h b/source/base/LinkedList.h
new file mode 100644
index 0000000..1b5b851
--- /dev/null
+++ b/source/base/LinkedList.h
@@ -0,0 +1,98 @@
+//
+// LinkedList.h - MrsWatson
+// Created by Nik Reiman on 1/3/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+
+#ifndef MrsWatson_LinkedList_h
+#define MrsWatson_LinkedList_h
+
+typedef struct {
+ void *item;
+ void *nextItem;
+
+ // This field should be considered private, and is only valid for the head node
+ int _numItems;
+} LinkedListMembers;
+
+typedef LinkedListMembers *LinkedList;
+typedef LinkedListMembers *LinkedListIterator;
+
+typedef void (*LinkedListForeachFunc)(void *item, void *userData);
+typedef void (*LinkedListFreeItemFunc)(void *item);
+
+/**
+ * Create a new linked list
+ * @return Linked list with no items
+ */
+LinkedList newLinkedList(void);
+
+/**
+ * Add an item to the end of a list
+ * @param self
+ * @param item Item to append. Items should (but do not necessarily have to be)
+ * of the same type. However, if items in the list are not of the same type,
+ * using functions such as foreachItemInList() will be much more difficult.
+ */
+void linkedListAppend(LinkedList self, void *item);
+
+/**
+ * Get the number of items in a list. Use this function instead of accessing
+ * the fields of the list, as the field names or use may change in the future.
+ * @param self
+ * @return Number of items in the list
+ */
+int linkedListLength(LinkedList self);
+
+/**
+ * Flatten a LinkedList to an array. The resulting array will be size N + 1,
+ * with a NULL object at the end of the array.
+ * @param self
+ * @return Array of void* objects with terminating NULL member
+ */
+void **linkedListToArray(LinkedList self);
+
+/**
+ * Iterate over each item in a linked list, calling the given function on each item.
+ * @param self
+ * @param foreachFunc Function to call
+ * @param userData User data to pass to the function
+ */
+void linkedListForeach(LinkedList self, LinkedListForeachFunc foreachFunc, void *userData);
+
+/**
+ * Free each item in a linked list. The contents of the items themselves are *not*
+ * freed. To do that, call freeLinkedListAndItems() instead.
+ * @param self
+ */
+void freeLinkedList(LinkedList self);
+
+/**
+ * Free a linked list and each of its items.
+ * @param self
+ * @param freeItem Free function to be called for each item
+ */
+void freeLinkedListAndItems(LinkedList self, LinkedListFreeItemFunc freeItem);
+
+#endif
diff --git a/source/base/PlatformInfo.c b/source/base/PlatformInfo.c
new file mode 100644
index 0000000..44c45cc
--- /dev/null
+++ b/source/base/PlatformInfo.c
@@ -0,0 +1,272 @@
+//
+// PlatformInfo.c - MrsWatson
+// Created by Nik Reiman on 1/2/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include <stdio.h>
+#include <string.h>
+
+#include "base/PlatformInfo.h"
+#include "logging/EventLogger.h"
+
+#if LINUX
+#include <sys/utsname.h>
+#include "base/File.h"
+
+#define LSB_FILE_PATH "/etc/lsb-release"
+#define LSB_DISTRIBUTION "DISTRIB_DESCRIPTION"
+#endif
+
+static PlatformType _getPlatformType()
+{
+#if MACOSX
+ return PLATFORM_MACOSX;
+#elif WINDOWS
+ return PLATFORM_WINDOWS;
+#elif LINUX
+ return PLATFORM_LINUX;
+#else
+ return PLATFORM_UNSUPPORTED;
+#endif
+}
+
+static const char *_getShortPlatformName(void)
+{
+#if MACOSX
+ return "Mac OS X";
+#elif WINDOWS
+
+ if (platformInfoIsRuntime64Bit()) {
+ return "Windows 64-bit";
+ } else {
+ return "Windows 32-bit";
+ }
+
+#elif LINUX
+
+ if (platformInfoIsRuntime64Bit()) {
+ return "Linux-x86_64";
+ } else {
+ return "Linux-i686";
+ }
+
+#else
+ return "Unsupported";
+#endif
+}
+
+#if LINUX
+static void _findLsbDistribution(void *item, void *userData)
+{
+ CharString line = (CharString)item;
+ CharString distributionName = (CharString)userData;
+ LinkedList tokens = charStringSplit(line, '=');
+
+ if (tokens != NULL && linkedListLength(tokens) == 2) {
+ CharString *tokensArray = (CharString *)linkedListToArray(tokens);
+ CharString key = tokensArray[0];
+ CharString value = tokensArray[1];
+
+ if (!strcmp(key->data, LSB_DISTRIBUTION)) {
+ charStringCopy(distributionName, value);
+ }
+
+ free(tokensArray);
+ }
+
+ freeLinkedListAndItems(tokens, (LinkedListFreeItemFunc)freeCharString);
+}
+#endif
+
+#if MACOSX
+extern void _getMacVersionString(CharString outString);
+#endif
+
+static CharString _getPlatformName(void)
+{
+ CharString result = newCharString();
+#if MACOSX
+ charStringCopyCString(result, _getShortPlatformName());
+ _getMacVersionString(result);
+#elif LINUX
+ CharString distributionName = newCharString();
+ struct utsname systemInfo;
+ File lsbRelease = NULL;
+ LinkedList lsbReleaseLines = NULL;
+
+ if (uname(&systemInfo) != 0) {
+ logWarn("Could not get system information from uname");
+ charStringCopyCString(result, "Linux (Unknown platform)");
+ freeCharString(distributionName);
+ return result;
+ }
+
+ charStringCopyCString(distributionName, "(Unknown distribution)");
+
+ lsbRelease = newFileWithPathCString(LSB_FILE_PATH);
+
+ if (fileExists(lsbRelease)) {
+ lsbReleaseLines = fileReadLines(lsbRelease);
+
+ if (lsbReleaseLines != NULL && linkedListLength(lsbReleaseLines) > 0) {
+ linkedListForeach(lsbReleaseLines, _findLsbDistribution, distributionName);
+ }
+ }
+
+ if (charStringIsEmpty(result)) {
+ snprintf(result->data, result->capacity, "Linux %s, kernel %s %s",
+ distributionName->data, systemInfo.release, systemInfo.machine);
+ }
+
+ freeCharString(distributionName);
+ freeLinkedListAndItems(lsbReleaseLines, (LinkedListFreeItemFunc)freeCharString);
+ freeFile(lsbRelease);
+#elif WINDOWS
+ OSVERSIONINFOEX versionInformation;
+ memset(&versionInformation, 0, sizeof(OSVERSIONINFOEX));
+ versionInformation.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
+ GetVersionEx((OSVERSIONINFO *)&versionInformation);
+ // Generic string which will also work with newer versions of windows
+ snprintf(result->data, result->capacity, "Windows %d.%d",
+ versionInformation.dwMajorVersion, versionInformation.dwMinorVersion);
+
+ // This is a bit lame, but it seems that this is the standard way of getting
+ // the platform name on Windows.
+ switch (versionInformation.dwMajorVersion) {
+ case 6:
+ switch (versionInformation.dwMinorVersion) {
+ case 2:
+ charStringCopyCString(result, "Windows 8");
+ break;
+
+ case 1:
+ charStringCopyCString(result, "Windows 7");
+ break;
+
+ case 0:
+ charStringCopyCString(result, "Windows Vista");
+ break;
+ }
+
+ break;
+
+ case 5:
+ switch (versionInformation.dwMinorVersion) {
+ case 2:
+ charStringCopyCString(result, "Windows Server 2003");
+ break;
+
+ case 1:
+ charStringCopyCString(result, "Windows XP");
+ break;
+
+ case 0:
+ charStringCopyCString(result, "Windows 2000");
+ break;
+ }
+
+ break;
+ }
+
+#else
+ charStringCopyCString(result, "Unsupported platform");
+#endif
+
+ return result;
+}
+
+boolByte platformInfoIsRuntime64Bit(void)
+{
+ return (boolByte)(sizeof(void *) == 8);
+}
+
+boolByte platformInfoIsHost64Bit(void)
+{
+ boolByte result = false;
+
+#if LINUX
+ struct utsname systemInfo;
+
+ if (uname(&systemInfo) != 0) {
+ logError("Could not get system bitness from uname");
+ } else {
+ result = (boolByte)(strcmp(systemInfo.machine, "x86_64") == 0);
+ }
+
+#elif MACOSX
+#elif WINDOWS
+ typedef BOOL (WINAPI * IsWow64ProcessFuncPtr)(HANDLE, PBOOL);
+ BOOL isProcessRunningInWow64 = false;
+ IsWow64ProcessFuncPtr isWow64ProcessFunc = NULL;
+
+ // The IsWow64Process() function is not available on all versions of Windows,
+ // so it must be looked up first and called only if it exists.
+ isWow64ProcessFunc = (IsWow64ProcessFuncPtr)GetProcAddress(GetModuleHandle(TEXT("kernel32")), "IsWow64Process");
+
+ if (isWow64ProcessFunc != NULL) {
+ if (isWow64ProcessFunc(GetCurrentProcess(), &isProcessRunningInWow64)) {
+ // IsWow64Process will only return true if the current process is a 32-bit
+ // application running on 64-bit Windows.
+ if (isProcessRunningInWow64) {
+ result = true;
+ } else {
+ // If false, then we can assume that the host has the same bitness as
+ // the executable.
+ result = platformInfoIsRuntime64Bit();
+ }
+ }
+ }
+
+#else
+ logUnsupportedFeature("Get host 64-bitness");
+#endif
+
+ return result;
+}
+
+boolByte platformInfoIsLittleEndian(void)
+{
+ int num = 1;
+ return (boolByte)(*(char *)&num == 1);
+}
+
+PlatformInfo newPlatformInfo(void)
+{
+ PlatformInfo platformInfo = (PlatformInfo)malloc(sizeof(PlatformInfoMembers));
+ platformInfo->type = _getPlatformType();
+ platformInfo->name = _getPlatformName();
+ platformInfo->shortName = newCharStringWithCString(_getShortPlatformName());
+ platformInfo->is64Bit = platformInfoIsHost64Bit();
+ return platformInfo;
+}
+
+void freePlatformInfo(PlatformInfo self)
+{
+ if (self != NULL) {
+ freeCharString(self->name);
+ freeCharString(self->shortName);
+ free(self);
+ }
+}
diff --git a/source/base/PlatformInfo.h b/source/base/PlatformInfo.h
new file mode 100644
index 0000000..cd706d6
--- /dev/null
+++ b/source/base/PlatformInfo.h
@@ -0,0 +1,68 @@
+//
+// PlatformInfo.h - MrsWatson
+// Created by Nik Reiman on 14/12/14.
+// Copyright (c) 2014 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+
+#ifndef MrsWatson_PlatformInfo_h
+#define MrsWatson_PlatformInfo_h
+
+#include "base/CharString.h"
+
+typedef enum {
+ PLATFORM_UNSUPPORTED,
+ PLATFORM_MACOSX,
+ PLATFORM_WINDOWS,
+ PLATFORM_LINUX,
+ NUM_PLATFORMS
+} PlatformType;
+
+typedef struct {
+ PlatformType type;
+ CharString name;
+ CharString shortName;
+ boolByte is64Bit;
+} PlatformInfoMembers;
+typedef PlatformInfoMembers *PlatformInfo;
+
+PlatformInfo newPlatformInfo(void);
+
+/**
+ * @brief Static method which returns true if the host CPU is little endian
+ */
+boolByte platformInfoIsLittleEndian(void);
+
+/**
+ * @brief True if the platform is a native 64-bit OS
+ */
+boolByte platformInfoIsHost64Bit(void);
+
+/**
+ * @brief True if the executable is running as a 64-bit binary
+ */
+boolByte platformInfoIsRuntime64Bit(void);
+
+void freePlatformInfo(PlatformInfo self);
+
+#endif
diff --git a/source/base/PlatformInfoMac.m b/source/base/PlatformInfoMac.m
new file mode 100644
index 0000000..2bf0fd9
--- /dev/null
+++ b/source/base/PlatformInfoMac.m
@@ -0,0 +1,41 @@
+//
+// PlatformInfoMac.mm - MrsWatson
+// Created by Nik Reiman on 5/12/14.
+// Copyright (c) 2014 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+
+#if MACOSX
+
+#include <Foundation/Foundation.h>
+
+#include "base/CharString.h"
+
+void _getMacVersionString(CharString outString);
+void _getMacVersionString(CharString outString) {
+ NSString *osVersion = [[NSProcessInfo processInfo] operatingSystemVersionString];
+ const char *osVersionCString = [osVersion UTF8String];
+ charStringAppendCString(outString, osVersionCString);
+}
+
+#endif
diff --git a/source/base/Types.h b/source/base/Types.h
new file mode 100644
index 0000000..c1772ef
--- /dev/null
+++ b/source/base/Types.h
@@ -0,0 +1,89 @@
+//
+// Types.h - MrsWatson
+// Created by Nik Reiman on 1/2/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+
+#ifndef MrsWatson_Types_h
+#define MrsWatson_Types_h
+
+// Custom types used across the application
+typedef int PcmSample; // TODO: int32_t?
+typedef float Sample;
+typedef Sample *Samples;
+
+typedef double SampleRate;
+typedef double Tempo;
+typedef unsigned long SampleCount;
+typedef unsigned short ChannelCount;
+
+// Using "bool" or "boolByte" (or their uppercase equivalents) is a bit dangerous
+// since compilers on some platforms define this for us. This gets tricky when
+// mixing C89/C99 syntax, so to be safe, we will use a new made-up type instead.
+typedef unsigned char boolByte;
+
+#ifndef byte
+typedef unsigned char byte;
+#endif
+
+#ifndef false
+#define false 0
+#endif
+
+#ifndef true
+#define true 1
+#endif
+
+// Platform-specific hooks or compiler overrides
+#if WINDOWS
+#define WIN32_LEAN_AND_MEAN 1
+#include <Windows.h>
+
+// Even redefining most of the functions below doesn't stop the compiler
+// from nagging about them.
+#pragma warning(disable: 4996)
+
+// Substitutes for POSIX functions not found on Windows
+#define strcasecmp _stricmp
+#define strdup _strdup
+#define unlink _unlink
+#define snprintf _snprintf
+#define isatty _isatty
+#define chdir _chdir
+#define unlink _unlink
+#endif
+
+// LibraryHandle definition
+#if MACOSX
+#include <CoreFoundation/CFBundle.h>
+typedef CFBundleRef LibraryHandle;
+#elif LINUX
+typedef void *LibraryHandle;
+#elif WINDOWS
+typedef HMODULE LibraryHandle;
+#else
+typedef void *LibraryHandle;
+#endif
+
+#endif