Browse Source

Using the file API to save and load raw buffers.

David Piuva 5 years ago
parent
commit
2df2c9ee42

+ 41 - 9
Source/DFPSR/api/bufferAPI.cpp

@@ -21,9 +21,11 @@
 //    3. This notice may not be removed or altered from any source
 //    3. This notice may not be removed or altered from any source
 //    distribution.
 //    distribution.
 
 
+#include <fstream>
 #include <cstdlib>
 #include <cstdlib>
 #include "bufferAPI.h"
 #include "bufferAPI.h"
 #include "../math/scalar.h"
 #include "../math/scalar.h"
+#include "../base/text.h"
 
 
 namespace dsr {
 namespace dsr {
 
 
@@ -89,33 +91,63 @@ BufferImpl::~BufferImpl() {
 // API
 // API
 
 
 Buffer buffer_clone(Buffer buffer) {
 Buffer buffer_clone(Buffer buffer) {
-	Buffer newBuffer = std::make_shared<BufferImpl>(buffer->size);
-	memcpy(newBuffer->data, buffer->data, buffer->size);
-	return newBuffer;
+	if (!buffer_exists(buffer)) {
+		return Buffer();
+	} else {
+		Buffer newBuffer = std::make_shared<BufferImpl>(buffer->size);
+		memcpy(newBuffer->data, buffer->data, buffer->size);
+		return newBuffer;
+	}
 }
 }
 
 
 Buffer buffer_create(int64_t newSize) {
 Buffer buffer_create(int64_t newSize) {
-	return std::make_shared<BufferImpl>(newSize);
+	if (newSize <= 0) {
+		throwError(U"buffer_create: Cannot create buffer of size ", newSize, U".\n");
+		return Buffer();
+	} else {
+		return std::make_shared<BufferImpl>(newSize);
+	}
 }
 }
 
 
 Buffer buffer_create(int64_t newSize, uint8_t *newData) {
 Buffer buffer_create(int64_t newSize, uint8_t *newData) {
-	return std::make_shared<BufferImpl>(newSize, newData);
+	if (newSize <= 0) {
+		throwError(U"buffer_create: Cannot create buffer of size ", newSize, U" (with existing data).\n");
+		return Buffer();
+	} else {
+		return std::make_shared<BufferImpl>(newSize, newData);
+	}
 }
 }
 
 
 void buffer_replaceDestructor(Buffer buffer, const std::function<void(uint8_t *)>& newDestructor) {
 void buffer_replaceDestructor(Buffer buffer, const std::function<void(uint8_t *)>& newDestructor) {
-	buffer->destructor = newDestructor;
+	if (!buffer_exists(buffer)) {
+		throwError(U"buffer_replaceDestructor: Cannot replace destructor for a buffer that don't exist.\n");
+	} else {
+		buffer->destructor = newDestructor;
+	}
 }
 }
 
 
 int64_t buffer_getSize(Buffer buffer) {
 int64_t buffer_getSize(Buffer buffer) {
-	return buffer->size;
+	if (!buffer_exists(buffer)) {
+		return 0;
+	} else {
+		return buffer->size;
+	}
 }
 }
 
 
 uint8_t* buffer_dangerous_getUnsafeData(Buffer buffer) {
 uint8_t* buffer_dangerous_getUnsafeData(Buffer buffer) {
-	return buffer->data;
+	if (!buffer_exists(buffer)) {
+		return nullptr;
+	} else {
+		return buffer->data;
+	}
 }
 }
 
 
 void buffer_setBytes(Buffer buffer, uint8_t value) {
 void buffer_setBytes(Buffer buffer, uint8_t value) {
-	memset(buffer->data, value, buffer->bufferSize);
+	if (!buffer_exists(buffer)) {
+		throwError(U"buffer_setBytes: Cannot set bytes for a buffer that don't exist.\n");
+	} else {
+		memset(buffer->data, value, buffer->bufferSize);
+	}
 }
 }
 
 
 }
 }

+ 15 - 3
Source/DFPSR/api/bufferAPI.h

@@ -37,39 +37,51 @@ namespace dsr {
 	using Buffer = std::shared_ptr<BufferImpl>;
 	using Buffer = std::shared_ptr<BufferImpl>;
 
 
 	// Creates a new buffer of newSize bytes.
 	// Creates a new buffer of newSize bytes.
+	// Pre-condition: newSize > 0
 	Buffer buffer_create(int64_t newSize);
 	Buffer buffer_create(int64_t newSize);
 
 
 	// Creates a new buffer of newSize bytes inheriting ownership of newData.
 	// Creates a new buffer of newSize bytes inheriting ownership of newData.
 	//   If the given data cannot be freed as a C allocation, replaceDestructor must be called with the special destructor.
 	//   If the given data cannot be freed as a C allocation, replaceDestructor must be called with the special destructor.
+	// Pre-condition: newSize > 0
 	Buffer buffer_create(int64_t newSize, uint8_t *newData);
 	Buffer buffer_create(int64_t newSize, uint8_t *newData);
 
 
 	// Sets the allocation's destructor, to be called when there are no more reference counted pointers to the buffer.
 	// Sets the allocation's destructor, to be called when there are no more reference counted pointers to the buffer.
+	// Pre-condition: buffer exists
 	void buffer_replaceDestructor(Buffer buffer, const std::function<void(uint8_t *)>& newDestructor);
 	void buffer_replaceDestructor(Buffer buffer, const std::function<void(uint8_t *)>& newDestructor);
 
 
-	// Returns true iff the handle points to a buffer
+	// Returns true iff buffer exists
 	inline bool buffer_exists(Buffer buffer) {
 	inline bool buffer_exists(Buffer buffer) {
 		return buffer.get() != nullptr;
 		return buffer.get() != nullptr;
 	}
 	}
 
 
 	// Returns a clone of the buffer.
 	// Returns a clone of the buffer.
+	// Giving an empty handle returns an empty handle
 	Buffer buffer_clone(Buffer buffer);
 	Buffer buffer_clone(Buffer buffer);
 
 
 	// Returns the buffer's size in bytes, as given when allocating it excluding allocation padding
 	// Returns the buffer's size in bytes, as given when allocating it excluding allocation padding
+	// Returns zero if buffer doesn't exist
 	int64_t buffer_getSize(Buffer buffer);
 	int64_t buffer_getSize(Buffer buffer);
 
 
 	// Returns a raw pointer to the data.
 	// Returns a raw pointer to the data.
+	// An empty handle will return nullptr.
 	uint8_t* buffer_dangerous_getUnsafeData(Buffer buffer);
 	uint8_t* buffer_dangerous_getUnsafeData(Buffer buffer);
 
 
 	// A wrapper for getting a bound-checked pointer of the correct element type.
 	// A wrapper for getting a bound-checked pointer of the correct element type.
 	//   Only cast to trivially packed types with power of two dimensions so that the compiler does not add padding.
 	//   Only cast to trivially packed types with power of two dimensions so that the compiler does not add padding.
 	// The name must be an ansi encoded constant literal, because each String contains a Buffer which would cause a cyclic dependency.
 	// The name must be an ansi encoded constant literal, because each String contains a Buffer which would cause a cyclic dependency.
+	// Returns a safe null pointer if buffer does not exist.
 	template <typename T>
 	template <typename T>
 	SafePointer<T> buffer_getSafeData(Buffer buffer, const char* name) {
 	SafePointer<T> buffer_getSafeData(Buffer buffer, const char* name) {
-		uint8_t *data = buffer_dangerous_getUnsafeData(buffer);
-		return SafePointer<T>(name, (T*)data, buffer_getSize(buffer), (T*)data);
+		if (!buffer_exists(buffer)) {
+			return SafePointer<T>();
+		} else {
+			uint8_t *data = buffer_dangerous_getUnsafeData(buffer);
+			return SafePointer<T>(name, (T*)data, buffer_getSize(buffer), (T*)data);
+		}
 	}
 	}
 
 
 	// Set all bytes to the same value
 	// Set all bytes to the same value
+	// Pre-condition: buffer exists
 	void buffer_setBytes(Buffer buffer, uint8_t value);
 	void buffer_setBytes(Buffer buffer, uint8_t value);
 }
 }
 
 

+ 84 - 0
Source/DFPSR/api/fileAPI.cpp

@@ -0,0 +1,84 @@
+// zlib open source license
+//
+// Copyright (c) 2020 David Forsgren Piuva
+// 
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+// 
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+// 
+//    1. The origin of this software must not be misrepresented; you must not
+//    claim that you wrote the original software. If you use this software
+//    in a product, an acknowledgment in the product documentation would be
+//    appreciated but is not required.
+// 
+//    2. Altered source versions must be plainly marked as such, and must not be
+//    misrepresented as being the original software.
+// 
+//    3. This notice may not be removed or altered from any source
+//    distribution.
+
+#include <fstream>
+#include <cstdlib>
+#include "bufferAPI.h"
+#include "../math/scalar.h"
+#include "../base/text.h"
+
+namespace dsr {
+
+// TODO: Try converting to UTF-8 for file names, which would only have another chance at working
+static char toAscii(DsrChar c) {
+	if (c > 127) {
+		return '?';
+	} else {
+		return c;
+	}
+}
+#define TO_RAW_ASCII(TARGET, SOURCE) \
+	char TARGET[SOURCE.length() + 1]; \
+	for (int i = 0; i < SOURCE.length(); i++) { \
+		TARGET[i] = toAscii(SOURCE[i]); \
+	} \
+	TARGET[SOURCE.length()] = '\0';
+
+Buffer buffer_load(const ReadableString& filename, bool mustExist) {
+	// TODO: Load files using Unicode filenames when available
+	TO_RAW_ASCII(asciiFilename, filename);
+	std::ifstream fileStream(asciiFilename, std::ios_base::in | std::ios_base::binary);
+	if (fileStream.is_open()) {
+		// Get the file's length and allocate an array for the raw encoding
+		fileStream.seekg (0, fileStream.end);
+		int64_t fileLength = fileStream.tellg();
+		fileStream.seekg (0, fileStream.beg);
+		Buffer buffer = buffer_create(fileLength);
+		fileStream.read((char*)buffer_dangerous_getUnsafeData(buffer), fileLength);
+		return buffer;
+	} else {
+		if (mustExist) {
+			throwError(U"The text file ", filename, U" could not be opened for reading.\n");
+		}
+		// If the file cound not be found and opened, an empty buffer is returned
+		return Buffer();
+	}
+}
+
+void buffer_save(const ReadableString& filename, Buffer buffer) {
+	// TODO: Save files using Unicode filenames
+	if (!buffer_exists(buffer)) {
+		throwError(U"buffer_save: Cannot save a buffer that don't exist to a file.\n");
+	} else {
+		TO_RAW_ASCII(asciiFilename, filename);
+		std::ofstream fileStream(asciiFilename, std::ios_base::out | std::ios_base::binary);
+		if (fileStream.is_open()) {
+			fileStream.write((char*)buffer_dangerous_getUnsafeData(buffer), buffer_getSize(buffer));
+			fileStream.close();
+		} else {
+			throwError("Failed to save ", filename, "\n");
+		}
+	}
+}
+
+}

+ 45 - 0
Source/DFPSR/api/fileAPI.h

@@ -0,0 +1,45 @@
+// zlib open source license
+//
+// Copyright (c) 2020 David Forsgren Piuva
+// 
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+// 
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+// 
+//    1. The origin of this software must not be misrepresented; you must not
+//    claim that you wrote the original software. If you use this software
+//    in a product, an acknowledgment in the product documentation would be
+//    appreciated but is not required.
+// 
+//    2. Altered source versions must be plainly marked as such, and must not be
+//    misrepresented as being the original software.
+// 
+//    3. This notice may not be removed or altered from any source
+//    distribution.
+
+#ifndef DFPSR_API_FILE
+#define DFPSR_API_FILE
+
+#include "../base/text.h"
+#include "bufferAPI.h"
+
+// A module for file access that exists to prevent cyclic dependencies between strings and buffers.
+//   Buffers need a filename to be saved or loaded while strings use buffers to store their characters.
+
+namespace dsr {
+	// Post-condition:
+	//   Returns the content of the file referred to be filename.
+	//   If mustExist is true, then failure to load will throw an exception.
+	//   If mustExist is false, then failure to load will return an empty handle (returning false for buffer_exists).
+	Buffer buffer_load(const ReadableString& filename, bool mustExist = true);
+
+	// Side-effect: Saves buffer to filename as a binary file.
+	// Pre-condition: buffer exists
+	void buffer_save(const ReadableString& filename, Buffer buffer);
+}
+
+#endif

+ 11 - 44
Source/DFPSR/base/text.cpp

@@ -21,11 +21,12 @@
 //    3. This notice may not be removed or altered from any source
 //    3. This notice may not be removed or altered from any source
 //    distribution.
 //    distribution.
 
 
-#include "text.h"
 #include <fstream>
 #include <fstream>
 #include <streambuf>
 #include <streambuf>
 #include <cstring>
 #include <cstring>
 #include <stdexcept>
 #include <stdexcept>
+#include "text.h"
+#include "../api/fileAPI.h"
 
 
 using namespace dsr;
 using namespace dsr;
 
 
@@ -480,38 +481,11 @@ String dsr::string_loadFromMemory(Buffer fileContent) {
 // Loads a text file of unknown format
 // Loads a text file of unknown format
 //   Removes carriage-return characters to make processing easy with only line-feed for breaking lines
 //   Removes carriage-return characters to make processing easy with only line-feed for breaking lines
 String dsr::string_load(const ReadableString& filename, bool mustExist) {
 String dsr::string_load(const ReadableString& filename, bool mustExist) {
-	// TODO: Load files using Unicode filenames when available
-	TO_RAW_ASCII(asciiFilename, filename);
-	std::ifstream fileStream(asciiFilename, std::ios_base::in | std::ios_base::binary);
-	if (fileStream.is_open()) {
-		String result;
-		// Get the file's length and allocate an array for the raw encoding
-		fileStream.seekg (0, fileStream.end);
-		int64_t fileLength = fileStream.tellg();
-		fileStream.seekg (0, fileStream.beg);
-		uint8_t* buffer = (uint8_t*)malloc(fileLength);
-		fileStream.read((char*)buffer, fileLength);
-		// Measure the size of the result by scanning the content in advance
-		int64_t characterCount = 0;
-		UTF32WriterFunction measurer = [&characterCount](DsrChar character) {
-			characterCount++;
-		};
-		feedStringFromFileBuffer(measurer, buffer, fileLength);
-		// Pre-allocate the correct amount of memory based on the simulation
-		result.reserve(characterCount);
-		// Stream output to the result string
-		UTF32WriterFunction reciever = [&result](DsrChar character) {
-			result.appendChar(character);
-		};
-		feedStringFromFileBuffer(reciever, buffer, fileLength);
-		free(buffer);
-		return result;
-	} else {
-		if (mustExist) {
-			throwError(U"The text file ", filename, U" could not be opened for reading.\n");
-		}
-		// If the file cound not be found and opened, a null string is returned
+	Buffer encoded = buffer_load(filename, mustExist);
+	if (!buffer_exists(encoded)) {
 		return String();
 		return String();
+	} else {
+		return string_loadFromMemory(encoded);
 	}
 	}
 }
 }
 
 
@@ -637,19 +611,12 @@ static void encodeText(const ByteWriterFunction &receiver, String content) {
 		} \
 		} \
 	}
 	}
 
 
-// TODO: Encoding to a buffer first and sending it all at once will be faster on certain operating systems
+// Encoding to a buffer before saving all at once as a binary file.
+//   This tells the operating system how big the file is in advance and prevent the worst case of stalling for minutes!
 void dsr::string_save(const ReadableString& filename, const ReadableString& content, CharacterEncoding characterEncoding, LineEncoding lineEncoding) {
 void dsr::string_save(const ReadableString& filename, const ReadableString& content, CharacterEncoding characterEncoding, LineEncoding lineEncoding) {
-	// TODO: Load files using Unicode filenames
-	TO_RAW_ASCII(asciiFilename, filename);
-	std::ofstream fileStream(asciiFilename, std::ios_base::out | std::ios_base::binary);
-	ByteWriterFunction receiver = [&fileStream](uint8_t value) {
-		fileStream.write((const char*)&value, 1);
-	};
-	if (fileStream.is_open()) {
-		ENCODE_TEXT(receiver, content, characterEncoding, lineEncoding);
-		fileStream.close();
-	} else {
-		throwError("Failed to save ", filename, "\n");
+	Buffer buffer = string_saveToMemory(content, characterEncoding, lineEncoding);
+	if (buffer_exists(buffer)) {
+		buffer_save(filename, buffer);
 	}
 	}
 }
 }
 
 

+ 5 - 4
Source/DFPSR/base/text.h

@@ -298,21 +298,22 @@ double string_toDouble(const ReadableString& source);
 //   Returns the content of the file referred to be filename.
 //   Returns the content of the file referred to be filename.
 //   If mustExist is true, then failure to load will throw an exception.
 //   If mustExist is true, then failure to load will throw an exception.
 //   If mustExist is false, then failure to load will return an empty string.
 //   If mustExist is false, then failure to load will return an empty string.
+// If you want to handle files that are not found in a different way,
+//   it is easy to use buffer_load and string_loadFromMemory separatelly.
 String string_load(const ReadableString& filename, bool mustExist = true);
 String string_load(const ReadableString& filename, bool mustExist = true);
-// A version loading the text from a binary representation of the file's content instead of the filename.
-//   Makes it easier to test character encoding and load arbitrary files from archives.
+// Decode a text file from a buffer, which can be loaded using buffer_load.
 String string_loadFromMemory(Buffer fileContent);
 String string_loadFromMemory(Buffer fileContent);
 
 
 // Side-effect: Saves content to filename using the selected character and line encodings.
 // Side-effect: Saves content to filename using the selected character and line encodings.
 // Do not add carriage return characters yourself into strings, for these will be added automatically in the CrLf mode.
 // Do not add carriage return characters yourself into strings, for these will be added automatically in the CrLf mode.
 // The internal String type should only use UTF-32 with single line feeds for breaking lines.
 // The internal String type should only use UTF-32 with single line feeds for breaking lines.
 //   This makes text processing algorithms a lot cleaner when a character or line break is always one element.
 //   This makes text processing algorithms a lot cleaner when a character or line break is always one element.
-// UTF-8 with BOM is default by being both compact and capable of storing 21 bits of unicode
+// UTF-8 with BOM is default by being both compact and capable of storing 21 bits of unicode.
 void string_save(const ReadableString& filename, const ReadableString& content,
 void string_save(const ReadableString& filename, const ReadableString& content,
   CharacterEncoding characterEncoding = CharacterEncoding::BOM_UTF8,
   CharacterEncoding characterEncoding = CharacterEncoding::BOM_UTF8,
   LineEncoding lineEncoding = LineEncoding::CrLf
   LineEncoding lineEncoding = LineEncoding::CrLf
 );
 );
-// A version encoding the text to a new buffer
+// Encode the string and keep the raw buffer instead of saving it to a file.
 Buffer string_saveToMemory(const ReadableString& content,
 Buffer string_saveToMemory(const ReadableString& content,
   CharacterEncoding characterEncoding = CharacterEncoding::BOM_UTF8,
   CharacterEncoding characterEncoding = CharacterEncoding::BOM_UTF8,
   LineEncoding lineEncoding = LineEncoding::CrLf
   LineEncoding lineEncoding = LineEncoding::CrLf

+ 3 - 2
Source/DFPSR/includeFramework.h

@@ -4,7 +4,7 @@
 #ifndef DFPSR_INCLUDED_FRAMEWORK
 #ifndef DFPSR_INCLUDED_FRAMEWORK
 #define DFPSR_INCLUDED_FRAMEWORK
 #define DFPSR_INCLUDED_FRAMEWORK
 
 
-	// Needed to use the APIs (exposed value types that will never change)
+	// Types needed to use the APIs
 	#include "math/includeMath.h"
 	#include "math/includeMath.h"
 	#include "base/text.h"
 	#include "base/text.h"
 
 
@@ -22,7 +22,8 @@
 	#include "api/mediaMachineAPI.h" // A machine for running image functions
 	#include "api/mediaMachineAPI.h" // A machine for running image functions
 	#include "api/fontAPI.h" // Printing text to images
 	#include "api/fontAPI.h" // Printing text to images
 	// File API
 	// File API
-	#include "api/bufferAPI.h" // Printing text to images
+	#include "api/bufferAPI.h" // Storing binary data
+	#include "api/fileAPI.h" // Saving and loading binary files
 	// Convenient API
 	// Convenient API
 	#include "api/timeAPI.h" // Methods for time and delays
 	#include "api/timeAPI.h" // Methods for time and delays
 	#include "api/configAPI.h" // Making it easy to load your application's settings from configuration files
 	#include "api/configAPI.h" // Making it easy to load your application's settings from configuration files