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