1 /**
2  * ae.sys.dataio
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <ae@cy.md>
12  */
13 
14 module ae.sys.dataio;
15 
16 import std.conv : to;
17 import std.exception : enforce;
18 
19 import ae.sys.data;
20 
21 // ************************************************************************
22 
23 deprecated("std.stream is deprecated, use readFileData")
24 template readStreamData()
25 {
26 	static import std.stream;
27 
28 	Data readStreamData(std.stream.Stream s)
29 	{
30 		auto size = s.size - s.position;
31 		assert(size < size_t.max);
32 		auto data = Data(cast(size_t)size);
33 		s.readExact(data.mptr, data.length);
34 		return data;
35 	}
36 }
37 
38 // ************************************************************************
39 
40 static import std.stdio;
41 
42 /// Read the entire file `f` into a `Data` instance.
43 /// If `f` does not have a size, read it in 1MB chunks.
44 Data readFileData(std.stdio.File f)
45 {
46 	Data result;
47 	auto size = f.size;
48 	if (size == ulong.max)
49 	{
50 		Data buf = Data(1024*1024);
51 		buf.asDataOf!ubyte.enter((contents) {
52 			while (!f.eof())
53 				result ~= f.rawRead(contents);
54 		});
55 	}
56 	else
57 	{
58 		auto pos = f.tell;
59 		ulong remaining = size - pos;
60 		if (!remaining)
61 			return Data();
62 		enforce(remaining <= ulong(size_t.max), "File does not fit in memory");
63 		result = Data(remaining.to!size_t);
64 		size_t bytesRead;
65 		result.asDataOf!ubyte.enter((contents) {
66 			bytesRead = f.rawRead(contents).length;
67 		});
68 		result.length = bytesRead;
69 	}
70 	return result;
71 }
72 
73 /// Read the entire file at the given `filename` into a `Data` instance.
74 Data readData(string filename)
75 {
76 	auto f = std.stdio.File(filename, "rb");
77 	return readFileData(f);
78 }
79 
80 // ************************************************************************
81 
82 /// Wrapper for Data class, allowing an object to be swapped to disk
83 /// and automatically retreived as required.
84 
85 final class SwappedData
86 {
87 	import std.file;
88 	import std.string;
89 	debug(SwappedData) import ae.sys.log;
90 
91 private:
92 	Data _data;
93 	string fileName;
94 	const(char)* cFileName;
95 
96 	static const MIN_SIZE = 4096; // minimum size to swap out
97 
98 	debug(SwappedData) static Logger log;
99 
100 public:
101 	this(string fileName)
102 	{
103 		debug(SwappedData) { if (log is null) log = fileAndConsoleLogger("SwappedData"); log(fileName ~ " - Creating"); }
104 		this.fileName = fileName;
105 		cFileName = fileName.toStringz();
106 		if (exists(fileName))
107 			remove(fileName);
108 	} ///
109 
110 	/// Swap out the contents.
111 	void unload()
112 	{
113 		if (!_data.empty && _data.length >= MIN_SIZE)
114 		{
115 			debug(SwappedData) log(fileName ~ " - Unloading");
116 			_data.enter((contents) {
117 				write(fileName, contents);
118 			});
119 			_data.clear();
120 		}
121 	}
122 
123 	/// Returns `true` if the contents is in memory.
124 	bool isLoaded()
125 	{
126 		return !exists(fileName); // wat
127 	}
128 
129 	/// Retrieves the contents, swapping it in if necessary.
130 	@property Data data()
131 	{
132 		if (!_data.length)
133 		{
134 			debug(SwappedData) log(fileName ~ " - Reloading");
135 			if (!exists(fileName))
136 				return Data();
137 			_data = readData(fileName);
138 			remove(fileName);
139 		}
140 		return _data;
141 	}
142 
143 	/// Sets the contents.
144 	@property void data(Data data)
145 	{
146 		debug(SwappedData) log(fileName ~ " - Setting");
147 		if (exists(fileName))
148 			remove(fileName);
149 		_data = data;
150 	}
151 
152 	/// Returns the size of the contents in bytes.
153 	size_t length()
154 	{
155 		if (!_data.empty)
156 			return _data.length;
157 		else
158 		if (exists(fileName))
159 			return cast(size_t)getSize(fileName);
160 		else
161 			return 0;
162 	}
163 
164 	~this() @nogc
165 	{
166 		// Can't allocate in destructors.
167 
168 		//debug(SwappedData) log(fileName ~ " - Destroying");
169 		/*if (exists(fileName))
170 		{
171 			debug(SwappedData) log(fileName ~ " - Deleting");
172 			remove(fileName);
173 		}*/
174 		import core.stdc.stdio : remove;
175 		remove(cFileName);
176 	}
177 }