1 /* FileInMemoryManager.cpp
2  *
3  * Copyright (C) 2017-2020 David Weenink
4  *
5  * This code is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or (at
8  * your option) any later version.
9  *
10  * This code is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this work. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "FileInMemoryManager.h"
20 #include "Collection.h"
21 
22 #include "oo_DESTROY.h"
23 #include "FileInMemoryManager_def.h"
24 #include "oo_COPY.h"
25 #include "FileInMemoryManager_def.h"
26 #include "oo_EQUAL.h"
27 #include "FileInMemoryManager_def.h"
28 #include "oo_CAN_WRITE_AS_ENCODING.h"
29 #include "FileInMemoryManager_def.h"
30 #include "oo_WRITE_TEXT.h"
31 #include "FileInMemoryManager_def.h"
32 #include "oo_READ_TEXT.h"
33 #include "FileInMemoryManager_def.h"
34 #include "oo_WRITE_BINARY.h"
35 #include "FileInMemoryManager_def.h"
36 #include "oo_READ_BINARY.h"
37 #include "FileInMemoryManager_def.h"
38 #include "oo_DESCRIPTION.h"
39 #include "FileInMemoryManager_def.h"
40 
41 #include <errno.h>
42 
43 /*
44 	File open and read emulations. The FILE * is internally used as a pointer to the index of the file in the Set.
45 	List of open files has to contain per file: index, position, length (bytes), pointer to data
46 */
47 
48 Thing_implement (FileInMemoryManager, Daata, 0);
49 
v_info()50 void structFileInMemoryManager :: v_info () {
51 	FileInMemoryManager_Parent :: v_info ();
52 	MelderInfo_writeLine (U"Number of files: ", files -> size);
53 	MelderInfo_writeLine (U"Total number of bytes: ", FileInMemorySet_getTotalNumberOfBytes (files.get()));
54 }
55 
FileInMemoryManager_hasDirectory(FileInMemoryManager me,conststring32 name)56 bool FileInMemoryManager_hasDirectory (FileInMemoryManager me, conststring32 name) {
57 		return FileInMemorySet_hasDirectory (my files.get(), name);
58 }
59 
FileInMemoryManager_create(FileInMemorySet files)60 autoFileInMemoryManager FileInMemoryManager_create (FileInMemorySet files) {
61 	try {
62 		autoFileInMemoryManager me = Thing_new (FileInMemoryManager);
63 		my files = Data_copy (files);
64 		my openFiles = FileInMemorySet_create ();
65 		my openFiles -> _initializeOwnership (false);
66 		return me;
67 	} catch (MelderError) {
68 		Melder_throw (U"");
69 	}
70 }
71 
72 /*
73 integer SortedSetOfLong_Lookup (SortedSetOfLong me, integer number) {
74 	if (my size == 0) return 0;   // empty set
75 	integer where = number - my at [my size] -> number;   // compare with last item
76 	if (where > 0) return 0;   // not at end
77 	if (where == 0) return my size;
78 	where = number - my at [1] -> number;   // compare with first item
79 	if (where < 0) return 0;   // not at start
80 	if (where == 0) return 1;
81 	integer left = 1, right = my size;
82 	while (left < right - 1) {
83 		integer mid = (left + right) / 2;
84 		where = number - my at [mid] -> number;
85 		if (where == 0) { // found
86 			return mid;
87 		} else if (where > 0) {
88 			left = mid;
89 		} else {
90 			right = mid;
91 		}
92 	}
93 	Melder_assert (right == left + 1);
94 	if ((number - my at [left] -> number) == 0) {
95 		return left;
96 	} else if ((number - my at [right] -> number) == 0) {
97 		return right;
98 	} else {
99 		return 0;
100 	}
101 }
102 */
103 
FileInMemoryManager_createFile(FileInMemoryManager me,MelderFile file)104 autoFileInMemory FileInMemoryManager_createFile (FileInMemoryManager me, MelderFile file) {
105 	try {
106 		autoFileInMemory thee = FileInMemory_create (file);
107 		return thee;
108 	} catch (MelderError) {
109 		Melder_throw (me, U"Cannot create a FileInMemory object.");
110 	}
111 }
112 
FileInMemoryManager_extractFiles(FileInMemoryManager me,kMelder_string which,conststring32 criterion)113 autoFileInMemorySet FileInMemoryManager_extractFiles (FileInMemoryManager me, kMelder_string which, conststring32 criterion) {
114 	return FileInMemorySet_extractFiles (my files.get(), which, criterion);
115 }
116 
_FileInMemoryManager_getIndexInOpenFiles(FileInMemoryManager me,FILE * stream)117 static integer _FileInMemoryManager_getIndexInOpenFiles (FileInMemoryManager me, FILE *stream) {
118 	const integer filesIndex = reinterpret_cast<integer> (stream);
119 	Melder_require (filesIndex > 0 && filesIndex <= my files -> size,
120 		U": Invalid file index: ", filesIndex);
121 
122 	const FileInMemory fim = static_cast<FileInMemory> (my files -> at [filesIndex]);
123 	const integer openFilesIndex = FileInMemorySet_lookUp (my openFiles.get(), fim -> d_path.get());
124 	return openFilesIndex;
125 }
126 
127 /*
128 	From http://www.cplusplus.com/reference/cstdio
129 	FILE * fopen ( const char * filename, const char * mode );
130 
131 	Open file
132 	Opens the file whose name is specified in the parameter filename and associates it with a stream that can be identified in future operations by the FILE pointer returned.
133 
134 	The operations that are allowed on the stream and how these are performed are defined by the mode parameter.
135 
136 	The returned stream is fully buffered by default if it is known to not refer to an interactive device (see setbuf).
137 
138 	The returned pointer can be disassociated from the file by calling fclose or freopen. All opened files are automatically closed on normal program termination.
139 
140 	The running environment supports at least FOPEN_MAX files open simultaneously.
141 
142 	Parameters
143 
144 	filename
145 		C string containing the name of the file to be opened.
146 		Its value shall follow the file name specifications of the running environment and can include a path (if supported by the system).
147 	mode
148 		C string containing a file access mode. It can be:
149 		"r"	read: Open file for input operations. The file must exist.
150 		"w"	write: Create an empty file for output operations. If a file with the same name already exists, its contents are discarded and the file is treated as a new empty file.
151 		"a"	append: Open file for output at the end of a file. Output operations always write data at the end of the file, expanding it. Repositioning operations (fseek, fsetpos, rewind) are ignored. The file is created if it does not exist.
152 		"r+"	read/update: Open a file for update (both for input and output). The file must exist.
153 		"w+"	write/update: Create an empty file and open it for update (both for input and output). If a file with the same name already exists its contents are discarded and the file is treated as a new empty file.
154 		"a+"	append/update: Open a file for update (both for input and output) with all output operations writing data at the end of the file. Repositioning operations (fseek, fsetpos, rewind) affects the next input operations, but output operations move the position back to the end of file. The file is created if it does not exist.
155 		With the mode specifiers above the file is open as a text file. In order to open a file as a binary file, a "b" character has to be included in the mode string. This additional "b" character can either be appended at the end of the string (thus making the following compound modes: "rb", "wb", "ab", "r+b", "w+b", "a+b") or be inserted between the letter and the "+" sign for the mixed modes ("rb+", "wb+", "ab+").
156 
157 		The new C standard (C2011, which is not part of C++) adds a new standard subspecifier ("x"), that can be appended to any "w" specifier (to form "wx", "wbx", "w+x" or "w+bx"/"wb+x"). This subspecifier forces the function to fail if the file exists, instead of overwriting it.
158 
159 		If additional characters follow the sequence, the behavior depends on the library implementation: some implementations may ignore additional characters so that for example an additional "t" (sometimes used to explicitly state a text file) is accepted.
160 
161 		On some library implementations, opening or creating a text file with update mode may treat the stream instead as a binary file.
162 
163 
164 	Text files are files containing sequences of lines of text. Depending on the environment where the application runs, some special character conversion may occur in input/output operations in text mode to adapt them to a system-specific text file format. Although on some environments no conversions occur and both text files and binary files are treated the same way, using the appropriate mode improves portability.
165 
166 	For files open for update (those which include a "+" sign), on which both input and output operations are allowed, the stream shall be flushed (fflush) or repositioned (fseek, fsetpos, rewind) before a reading operation that follows a writing operation. The stream shall be repositioned (fseek, fsetpos, rewind) before a writing operation that follows a reading operation (whenever that operation did not reach the end-of-file).
167 
168 	Return Value
169 	If the file is successfully opened, the function returns a pointer to a FILE object that can be used to identify the stream on future operations.
170 	Otherwise, a null pointer is returned.
171 	On most library implementations, the errno variable is also set to a system-specific error code on failure.
172 */
FileInMemoryManager_fopen(FileInMemoryManager me,const char * filename,const char * mode)173 FILE *FileInMemoryManager_fopen (FileInMemoryManager me, const char *filename, const char *mode) {
174 	try {
175 		integer index = 0;
176 		if (*mode == 'r') { // also covers mode == 'rb'
177 			index = FileInMemorySet_lookUp (my files.get(), Melder_peek8to32(filename));
178 			if (index > 0) {
179 				const FileInMemory fim = (FileInMemory) my files -> at [index];
180 				if (fim -> d_position == 0) // not open
181 					my openFiles -> addItem_ref (fim);
182 				else // reset position
183 					fim -> d_position = 0;
184 			} else {
185 				// file does not exist, set error condition?
186 			}
187 		} else if (*mode == 'w') {
188 
189 		}
190 		return reinterpret_cast<FILE *> (index);
191 	} catch (MelderError) {
192 		Melder_throw (U"File ", Melder_peek8to32(filename), U" cannot be opended.");
193 	}
194 }
195 
196 /*
197 	From http://www.cplusplus.com/reference/cstdio 20171028
198 	void rewind ( FILE * stream );
199 
200 	Set position of stream to the beginning
201 	Sets the position indicator associated with stream to the beginning of the file.
202 
203 	The end-of-file and error internal indicators associated to the stream are cleared after a successful call to this function, and all effects from previous calls to ungetc on this stream are dropped.
204 
205 	On streams open for update (read+write), a call to rewind allows to switch between reading and writing.
206 
207 	Parameters
208 
209 	stream
210 		Pointer to a FILE object that identifies the stream.
211 
212 
213 	Return Value
214 	none
215 */
FileInMemoryManager_rewind(FileInMemoryManager me,FILE * stream)216 void FileInMemoryManager_rewind (FileInMemoryManager me, FILE *stream) {
217 	const integer openFilesIndex = _FileInMemoryManager_getIndexInOpenFiles (me, stream);
218 	if (openFilesIndex > 0) {
219 		const FileInMemory fim = static_cast<FileInMemory> (my openFiles -> at [openFilesIndex]);
220 		fim -> d_position = 0;
221 		fim -> d_errno = 0;
222 		fim -> ungetChar = -1;
223 	}
224 }
225 
226 /*
227 	From http://www.cplusplus.com/reference/cstdio 20171028
228 	int fclose ( FILE * stream );
229 
230 	Close file
231 	Closes the file associated with the stream and disassociates it.
232 
233 	All internal buffers associated with the stream are disassociated from it and flushed: the content of any unwritten output buffer is written and the content of any unread input buffer is discarded.
234 
235 	Even if the call fails, the stream passed as parameter will no longer be associated with the file nor its buffers.
236 
237 	Parameters
238 
239 	stream
240 		Pointer to a FILE object that specifies the stream to be closed.
241 
242 
243 	Return Value
244 	If the stream is successfully closed, a zero value is returned.
245 	On failure, EOF is returned.
246 */
FileInMemoryManager_fclose(FileInMemoryManager me,FILE * stream)247 int FileInMemoryManager_fclose (FileInMemoryManager me, FILE *stream) {
248 	const integer openFilesIndex = _FileInMemoryManager_getIndexInOpenFiles (me, stream);
249 	if (openFilesIndex > 0) {
250 		const FileInMemory fim = static_cast<FileInMemory> (my openFiles -> at [openFilesIndex]);
251 		fim -> d_position = 0;
252 		fim -> d_errno = 0;
253 		fim -> ungetChar = -1;
254 		my openFiles -> removeItem (openFilesIndex);
255 	}
256 	return my errorNumber = 0; // always ok
257 }
258 
259 /*
260 	From http://www.cplusplus.com/reference/cstdio 20171028
261 	int feof ( FILE * stream );
262 
263 	Check end-of-file indicator
264 	Checks whether the end-of-File indicator associated with stream is set, returning a value different from zero if it is.
265 
266 	This indicator is generally set by a previous operation on the stream that attempted to read at or past the end-of-file.
267 
268 	Notice that stream's internal position indicator may point to the end-of-file for the next operation, but still, the end-of-file indicator may not be set until an operation attempts to read at that point.
269 
270 	This indicator is cleared by a call to clearerr, rewind, fseek, fsetpos or freopen. Although if the position indicator is not repositioned by such a call, the next i/o operation is likely to set the indicator again.
271 
272 	Parameters
273 
274 	stream
275 		Pointer to a FILE object that identifies the stream.
276 
277 
278 	Return Value
279 	A non-zero value is returned in the case that the end-of-file indicator associated with the stream is set.
280 	Otherwise, zero is returned.
281 */
FileInMemoryManager_feof(FileInMemoryManager me,FILE * stream)282 int FileInMemoryManager_feof (FileInMemoryManager me, FILE *stream) {
283 	const integer openFilesIndex = _FileInMemoryManager_getIndexInOpenFiles (me, stream);
284 	int eof = 0;
285 	if (openFilesIndex > 0) {
286 		const FileInMemory fim = static_cast<FileInMemory> (my openFiles -> at [openFilesIndex]);
287 		if (fim -> d_position >= fim -> d_numberOfBytes)
288 			eof = 1;
289 	}
290 	return eof;
291 }
292 
293 /*
294 	From http://www.cplusplus.com/reference/cstdio 20171028
295 	int fseek ( FILE * stream, long int offset, int origin );
296 
297 	Reposition stream position indicator
298 	Sets the position indicator associated with the stream to a new position.
299 
300 	For streams open in binary mode, the new position is defined by adding offset to a reference position specified by origin.
301 
302 	For streams open in text mode, offset shall either be zero or a value returned by a previous call to ftell, and origin shall necessarily be SEEK_SET.
303 
304 	If the function is called with other values for these arguments, support depends on the particular system and library implementation (non-portable).
305 
306 	The end-of-file internal indicator of the stream is cleared after a successful call to this function, and all effects from previous calls to ungetc on this stream are dropped.
307 
308 	On streams open for update (read+write), a call to fseek allows to switch between reading and writing.
309 
310 	Parameters
311 
312 	stream
313 		Pointer to a FILE object that identifies the stream.
314 	offset
315 		Binary files: Number of bytes to offset from origin.
316 		Text files: Either zero, or a value returned by ftell.
317 	origin
318 		Position used as reference for the offset. It is specified by one of the following constants defined in <cstdio> exclusively to be used as arguments for this function:
319 		Constant	Reference position
320 		SEEK_SET	Beginning of file
321 		SEEK_CUR	Current position of the file pointer
322 		SEEK_END	End of file *
323 		* Library implementations are allowed to not meaningfully support SEEK_END (therefore, code using it has no real standard portability).
324 
325 
326 	Return Value
327 	If successful, the function returns zero.
328 	Otherwise, it returns non-zero value.
329 	If a read or write error occurs, the error indicator (ferror) is set.
330 */
FileInMemoryManager_fseek(FileInMemoryManager me,FILE * stream,integer offset,int origin)331 int FileInMemoryManager_fseek (FileInMemoryManager me, FILE *stream, integer offset, int origin) {
332 	const integer openFilesIndex = _FileInMemoryManager_getIndexInOpenFiles (me, stream);
333 	int errval = EBADF;
334 	if (openFilesIndex > 0) {
335 		const FileInMemory fim = static_cast<FileInMemory> (my openFiles -> at [openFilesIndex]);
336 		integer newPosition = 0;
337 		if (origin == SEEK_SET)
338 			newPosition = offset;
339 		else if (origin == SEEK_CUR)
340 			newPosition = fim -> d_position + offset;
341 		else if (origin == SEEK_END)
342 			newPosition = fim -> d_numberOfBytes + offset;
343 		else
344 			return my errorNumber = EINVAL;
345 
346 		if (newPosition < 0) // > numberOfBytes is allowed
347 			newPosition = 0;
348 
349 		fim -> d_position = newPosition;
350 		fim -> ungetChar = -1;
351 		errval = 0;
352 	}
353 	return my errorNumber = errval;
354 }
355 
356 /*
357 	From http://www.cplusplus.com/reference/cstdio 20171028
358 	long int ftell ( FILE * stream );
359 
360 	Get current position in stream
361 	Returns the current value of the position indicator of the stream.
362 
363 	For binary streams, this is the number of bytes from the beginning of the file.
364 
365 	For text streams, the numerical value may not be meaningful but can still be used to restore the position to the same position later using fseek (if there are characters put back using ungetc still pending of being read, the behavior is undefined).
366 
367 	Parameters
368 
369 	stream
370 		Pointer to a FILE object that identifies the stream.
371 
372 
373 	Return Value
374 	On success, the current value of the position indicator is returned.
375 	On failure, -1L is returned, and errno is set to a system-specific positive value.
376 */
FileInMemoryManager_ftell(FileInMemoryManager me,FILE * stream)377 integer FileInMemoryManager_ftell (FileInMemoryManager me, FILE *stream) {
378 	const integer openFilesIndex = _FileInMemoryManager_getIndexInOpenFiles (me, stream);
379 	/* int errval = EBADF; */
380 	integer currentPosition = -1L;
381 	if (openFilesIndex > 0) {
382 		const FileInMemory fim = static_cast<FileInMemory> (my openFiles -> at [openFilesIndex]);
383 		currentPosition = fim -> d_position;
384 	}
385 	return currentPosition;
386 }
387 
388 /*
389 	From http://www.cplusplus.com/reference/cstdio 20171028
390 	char * fgets ( char * str, int num, FILE * stream );
391 
392 	Get string from stream
393 	Reads characters from stream and stores them as a C string into str until (num-1) characters have been read or either a newline or the end-of-file is reached, whichever happens first.
394 
395 	A newline character makes fgets stop reading, but it is considered a valid character by the function and included in the string copied to str.
396 
397 	A terminating null character is automatically appended after the characters copied to str.
398 
399 	Notice that fgets is quite different from gets: not only fgets accepts a stream argument, but also allows to specify the maximum size of str and includes in the string any ending newline character.
400 
401 	Parameters
402 
403 	str
404 		Pointer to an array of chars where the string read is copied.
405 	num
406 		Maximum number of characters to be copied into str (including the terminating null-character).
407 	stream
408 		Pointer to a FILE object that identifies an input stream.
409 		stdin can be used as argument to read from the standard input.
410 
411 
412 	Return Value
413 	On success, the function returns str.
414 	If the end-of-file is encountered while attempting to read a character, the eof indicator is set (feof). If this happens before any characters could be read, the pointer returned is a null pointer (and the contents of str remain unchanged).
415 	If a read error occurs, the error indicator (ferror) is set and a null pointer is also returned (but the contents pointed by str may have changed).
416  */
FileInMemoryManager_fgets(FileInMemoryManager me,char * str,int num,FILE * stream)417 char *FileInMemoryManager_fgets (FileInMemoryManager me, char *str, int num, FILE *stream) {
418 	const integer openFilesIndex = _FileInMemoryManager_getIndexInOpenFiles (me, stream);
419 	char *result = nullptr;
420 
421 	Melder_require (openFilesIndex > 0,
422 		U": File should be open.");
423 
424 	FileInMemory fim = static_cast<FileInMemory> (my openFiles -> at [openFilesIndex]);
425 	integer startPos = fim -> d_position;
426 	if (startPos < fim -> d_numberOfBytes) {
427 		integer i = 0, endPos = startPos + num;
428 		endPos = endPos < fim -> d_numberOfBytes ? endPos : fim -> d_numberOfBytes;
429 		const unsigned char * p = fim -> d_data.asArgumentToFunctionThatExpectsZeroBasedArray () + startPos;
430 		char *p_str = str;
431 		if (fim -> ungetChar > 0) {
432 			/*
433 				copy the ungetChar and advance one position in stream
434 			*/
435 			*p_str ++ = fim -> ungetChar;
436 			p ++;
437 			i ++;
438 			fim -> ungetChar = -1;
439 		}
440 		while (i ++ < num && (*p_str ++ = *p) && *p ++ != '\n');
441 		str [i] = '\0';
442 		fim -> d_position += i;
443 		result = str; // everything ok, return the str pointer
444 	} else {
445 		fim -> d_errno = EOF;
446 	}
447 	return result;
448 }
449 
450 /*
451 	From http://www.cplusplus.com/reference/cstdio 20171028
452 	int fgetc ( FILE * stream );
453 
454 	Get character from stream
455 	Returns the character currently pointed by the internal file position indicator of the specified stream. The internal file position indicator is then advanced to the next character.
456 
457 	If the stream is at the end-of-file when called, the function returns EOF and sets the end-of-file indicator for the stream (feof).
458 
459 	If a read error occurs, the function returns EOF and sets the error indicator for the stream (ferror).
460 
461 	fgetc and getc are equivalent, except that getc may be implemented as a macro in some libraries.
462 
463 	Parameters
464 
465 	stream
466 		Pointer to a FILE object that identifies an input stream.
467 
468 
469 	Return Value
470 	On success, the character read is returned (promoted to an int value).
471 	The return type is int to accommodate for the special value EOF, which indicates failure:
472 	If the position indicator was at the end-of-file, the function returns EOF and sets the eof indicator (feof) of stream.
473 	If some other reading error happens, the function also returns EOF, but sets its error indicator (ferror) instead.
474 */
FileInMemoryManager_fgetc(FileInMemoryManager me,FILE * stream)475 int FileInMemoryManager_fgetc (FileInMemoryManager me, FILE *stream) {
476 	char str [4];
477 	(void) FileInMemoryManager_fgets (me, str, 1, stream);
478 	return FileInMemoryManager_feof (me, stream) ? EOF : static_cast<int> (*str);
479 }
480 
481 /*
482 	From http://www.cplusplus.com/reference/cstdio 20171028
483 	size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
484 
485 	Read block of data from stream
486 	Reads an array of count elements, each one with a size of size bytes, from the stream and stores them in the block of memory specified by ptr.
487 
488 	The position indicator of the stream is advanced by the total amount of bytes read.
489 
490 	The total amount of bytes read if successful is (size*count).
491 
492 	Parameters
493 
494 	ptr
495 		Pointer to a block of memory with a size of at least (size*count) bytes, converted to a void*.
496 	size
497 		Size, in bytes, of each element to be read.
498 		size_t is an unsigned integral type.
499 	count
500 		Number of elements, each one with a size of size bytes.
501 		size_t is an unsigned integral type.
502 	stream
503 		Pointer to a FILE object that specifies an input stream.
504 
505 	Return Value
506 	The total number of elements successfully read is returned.
507 	If this number differs from the count parameter, either a reading error occurred or the end-of-file was reached while reading. In 	both cases, the proper indicator is set, which can be checked with ferror and feof, respectively.
508 	If either size or count is zero, the function returns zero and both the stream state and the content pointed by ptr remain unchanged.
509 	size_t is an unsigned integral type.
510 */
FileInMemoryManager_fread(FileInMemoryManager me,void * ptr,size_t size,size_t count,FILE * stream)511 size_t FileInMemoryManager_fread (FileInMemoryManager me, void *ptr, size_t size, size_t count, FILE *stream) {
512 	const integer openFilesIndex = _FileInMemoryManager_getIndexInOpenFiles (me, stream);
513 
514 	Melder_require (openFilesIndex > 0 && size > 0 && count > 0,
515 		U": File should be open.");
516 
517 	const FileInMemory fim = static_cast<FileInMemory> (my openFiles -> at [openFilesIndex]);
518 	size_t result = 0;
519 	integer startPos = fim -> d_position;
520 	if (startPos < fim -> d_numberOfBytes) {
521 		integer i = 0, endPos = startPos + count * size;
522 
523 		if (endPos > fim -> d_numberOfBytes) {
524 			count = (fim -> d_numberOfBytes - startPos) / size;
525 			endPos = startPos + count * size;
526 			fim -> d_errno = EOF;
527 		}
528 		const integer numberOfBytes = count * size;
529 		const unsigned char * p = fim -> d_data.asArgumentToFunctionThatExpectsZeroBasedArray () + fim -> d_position;
530 		char * str = static_cast<char *> (ptr);
531 		while (i < numberOfBytes)
532 			str [i ++] = *p ++;
533 		fim -> d_position = endPos;
534 	}
535 	result = count;
536 	return result;
537 }
538 
539 /*
540 	From http://www.cplusplus.com/reference/cstdio 20171028
541 	int ungetc ( int character, FILE * stream );
542 
543 	Unget character from stream
544 	A character is virtually put back into an input stream, decreasing its internal file position as if a previous getc operation was undone.
545 
546 	This character may or may not be the one read from the stream in the preceding input operation. In any case, the next character retrieved from stream is the character passed to this function, independently of the original one.
547 
548 	Notice though, that this only affects further input operations on that stream, and not the content of the physical file associated with it, which is not modified by any calls to this function.
549 
550 	Some library implementations may support this function to be called multiple times, making the characters available in the reverse order in which they were put back. Although this behavior has no standard portability guarantees, and further calls may simply fail after any number of calls beyond the first.
551 
552 	If successful, the function clears the end-of-file indicator of stream (if it was currently set), and decrements its internal file position indicator if it operates in binary mode; In text mode, the position indicator has unspecified value until all characters put back with ungetc have been read or discarded.
553 
554 	A call to fseek, fsetpos or rewind on stream will discard any characters previously put back into it with this function.
555 
556 	If the argument passed for the character parameter is EOF, the operation fails and the input stream remains unchanged.
557 
558 	Parameters
559 
560 	character
561 		The int promotion of the character to be put back.
562 		The value is internally converted to an unsigned char when put back.
563 	stream
564 		Pointer to a FILE object that identifies an input stream.
565 
566 
567 	Return Value
568 	On success, the character put back is returned.
569 	If the operation fails, EOF is returned.
570 */
571 
FileInMemoryManager_ungetc(FileInMemoryManager me,int character,FILE * stream)572 int FileInMemoryManager_ungetc (FileInMemoryManager me, int character, FILE * stream) {
573 	int result = EOF;
574 	if (character != EOF) {
575 		const integer openFilesIndex = _FileInMemoryManager_getIndexInOpenFiles (me, stream);
576 		if (openFilesIndex > 0) {
577 			const FileInMemory fim = static_cast<FileInMemory> (my openFiles -> at [openFilesIndex]);
578 			-- (fim -> d_position);
579 			result = fim -> ungetChar = character;
580 		}
581 	}
582 	return result;
583 }
584 
585 
586 /*
587 	From http://www.cplusplus.com/reference/cstdio 20171028
588 	int fprintf ( FILE * stream, const char * format, ... );
589 
590 	Write formatted data to stream
591 	Writes the C string pointed by format to the stream. If format includes format specifiers (subsequences beginning with %), the additional arguments following format are formatted and inserted in the resulting string replacing their respective specifiers.
592 
593 	After the format parameter, the function expects at least as many additional arguments as specified by format.
594 
595 	Parameters
596 
597 	stream
598 		Pointer to a FILE object that identifies an output stream.
599 	format
600 		C string that contains the text to be written to the stream.
601 		It can optionally contain embedded format specifiers that are replaced by the values specified in subsequent additional arguments and formatted as requested.
602 
603 		A format specifier follows this prototype:
604 
605 		%[flags][width][.precision][length] specifier
606 
607 		Where the specifier character at the end is the most significant component, since it defines the type and the interpretation of its corresponding argument:
608 		specifier	Output	Example
609 		d or i	Signed decimal integer	392
610 		u	Unsigned decimal integer	7235
611 		o	Unsigned octal	610
612 		x	Unsigned hexadecimal integer	7fa
613 		X	Unsigned hexadecimal integer (uppercase)	7FA
614 		f	Decimal floating point, lowercase	392.65
615 		F	Decimal floating point, uppercase	392.65
616 		e	Scientific notation (mantissa/exponent), lowercase	3.9265e+2
617 		E	Scientific notation (mantissa/exponent), uppercase	3.9265E+2
618 		g	Use the shortest representation: %e or %f	392.65
619 		G	Use the shortest representation: %E or %F	392.65
620 		a	Hexadecimal floating point, lowercase	-0xc.90fep-2
621 		A	Hexadecimal floating point, uppercase	-0XC.90FEP-2
622 		c	Character	a
623 		s	String of characters	sample
624 		p	Pointer address	b8000000
625 		n	Nothing printed.
626 		The corresponding argument must be a pointer to a signed int.
627 		The number of characters written so far is stored in the pointed location.
628 		%	A % followed by another % character will write a single % to the stream.	%
629 
630 		The format specifier can also contain sub-specifiers: flags, width, .precision and modifiers (in that order), which are optional and follow these specifications:
631 
632 		flags	description
633 		-	Left-justify within the given field width; Right justification is the default (see width sub-specifier).
634 		+	Forces to precede the result with a plus or minus sign (+ or -) even for positive numbers. By default, only negative numbers are preceded with a - sign.
635 		(space)	If no sign is going to be written, a blank space is inserted before the value.
636 		#	Used with o, x or X specifiers the value is preceded with 0, 0x or 0X respectively for values different than zero.
637 		Used with a, A, e, E, f, F, g or G it forces the written output to contain a decimal point even if no more digits follow. By default, if no digits follow, no decimal point is written.
638 		0	Left-pads the number with zeroes (0) instead of spaces when padding is specified (see width sub-specifier).
639 
640 		width	description
641 		(number)	Minimum number of characters to be printed. If the value to be printed is shorter than this number, the result is padded with blank spaces. The value is not truncated even if the result is larger.
642 		*	The width is not specified in the format string, but as an additional integer value argument preceding the argument that has to be formatted.
643 
644 		.precision	description
645 		.number	For integer specifiers (d, i, o, u, x, X): precision specifies the minimum number of digits to be written. If the value to be written is shorter than this number, the result is padded with leading zeros. The value is not truncated even if the result is longer. A precision of 0 means that no character is written for the value 0.
646 		For a, A, e, E, f and F specifiers: this is the number of digits to be printed after the decimal point (by default, this is 6).
647 		For g and G specifiers: This is the maximum number of significant digits to be printed.
648 		For s: this is the maximum number of characters to be printed. By default all characters are printed until the ending null character is encountered.
649 		If the period is specified without an explicit value for precision, 0 is assumed.
650 		.*	The precision is not specified in the format string, but as an additional integer value argument preceding the argument that has to be formatted.
651 
652 		The length sub-specifier modifies the length of the data type. This is a chart showing the types used to interpret the corresponding arguments with and without length specifier (if a different type is used, the proper type promotion or conversion is performed, if allowed):
653 			specifiers
654 		length	d i	u o x X	f F e E g G a A	c	s	p	n
655 		(none)	int	unsigned int	double	int	char*	void*	int*
656 		hh	signed char	unsigned char					signed char*
657 		h	short int	unsigned short int					short int*
658 		l	long int	unsigned long int		wint_t	wchar_t*		long int*
659 		ll	long long int	unsigned long long int					long long int*
660 		j	intmax_t	uintmax_t					intmax_t*
661 		z	size_t	size_t					size_t*
662 		t	ptrdiff_t	ptrdiff_t					ptrdiff_t*
663 		L			long double
664 		Note that the c specifier takes an int (or wint_t) as argument, but performs the proper conversion to a char value (or a wchar_t) before formatting it for output.
665 
666 		Note: Yellow rows indicate specifiers and sub-specifiers introduced by C99. See <cinttypes> for the specifiers for extended types.
667 	... (additional arguments)
668 		Depending on the format string, the function may expect a sequence of additional arguments, each containing a value to be used to replace a format specifier in the format string (or a pointer to a storage location, for n).
669 		There should be at least as many of these arguments as the number of values specified in the format specifiers. Additional arguments are ignored by the function.
670 
671 
672 	Return Value
673 		On success, the total number of characters written is returned.
674 
675 		If a writing error occurs, the error indicator (ferror) is set and a negative number is returned.
676 
677 	If a multibyte character encoding error occurs while writing wide characters, errno is set to EILSEQ and a negative number is returned.
678 */
FileInMemoryManager_fprintf(FileInMemoryManager me,FILE * stream,const char * format,...)679 int FileInMemoryManager_fprintf (FileInMemoryManager me, FILE * stream, const char *format, ... ) {
680 	(void) me;
681 	va_list args;
682 	if (stream == stderr) {
683 		va_start (args, format);
684 		int sizeNeeded = vsnprintf (nullptr, 0, format, args); // find size of needed buffer (without final null byte)
685 		const size_t bufferSize = sizeNeeded + 1;
686 		va_end (args);
687 		return bufferSize;
688 	}
689 	return -1;
690 }
691 
test_FileInMemoryManager_io(void)692 void test_FileInMemoryManager_io (void) {
693 	const conststring32 path1 = U"~/kanweg1.txt";
694 	const conststring32 path2 = U"~/kanweg2.txt";
695 	const conststring32 lines1 [3] = { U"abcd\n", U"ef\n",  U"ghijk\n" };
696 	const conststring32 lines2 [3] = { U"lmno\n", U"pqr\n",  U"stuvwxyz\n" };
697 	/*
698 		Create a test FileInMemorySet with two (text) files in it.
699 	*/
700 	MelderInfo_writeLine (U"test_FileInMemoryManager_io:");
701 	MelderInfo_writeLine (U"\tCreating two files: ", path1, U" and ", path2);
702 	structMelderFile s_file1 = {} , s_file2 = {};
703 	const MelderFile file1 = & s_file1, file2 = & s_file2;
704 	Melder_relativePathToFile (path1, file1);
705 	Melder_relativePathToFile (path2, file2);
706 	autoFileInMemorySet fims = FileInMemorySet_create ();
707 
708 	FILE *f = fopen (Melder_peek32to8 (file1 -> path), "w");
709 	for (integer j = 0; j <= 2; j ++)
710 		fputs (Melder_peek32to8 (lines1 [j]), f);
711 
712 	fclose (f);
713 
714 	f = fopen (Melder_peek32to8 (file2 -> path), "w");
715 	for (integer j = 0; j <= 2; j ++)
716 		fputs (Melder_peek32to8 (lines2 [j]), f);
717 
718 	fclose (f);
719 
720 	MelderInfo_writeLine (U"\tCreating FileInMemorySet from two files...");
721 
722 	autoFileInMemory fim1 = FileInMemory_create (file1);
723 	fims -> addItem_move (fim1.move());
724 	autoFileInMemory fim2 = FileInMemory_create (file2);
725 	fims -> addItem_move (fim2.move());
726 
727 	/*
728 		Create the FileInMemoryManager and test
729 	*/
730 
731 	autoFileInMemoryManager me = FileInMemoryManager_create (fims.get());
732 
733 	// fopen test
734 	MelderInfo_writeLine (U"\tOpen file ", file1 -> path);
735 	FILE * f1 = FileInMemoryManager_fopen (me.get(), Melder_peek32to8 (file1 -> path), "r");
736 	const integer openFilesIndex1 = _FileInMemoryManager_getIndexInOpenFiles (me.get(), f1);
737 	Melder_assert (openFilesIndex1 == 1);
738 	MelderInfo_writeLine (U"\t\t ...opened");
739 
740 	MelderInfo_writeLine (U"\tOpen file ", file2 -> path);
741 	FILE * f2 = FileInMemoryManager_fopen (me.get(), Melder_peek32to8 (file2 -> path), "r");
742 	const integer openFilesIndex2 = _FileInMemoryManager_getIndexInOpenFiles (me.get(), f2);
743 	Melder_assert (openFilesIndex2 == 2);
744 	MelderInfo_writeLine (U"\t\t ...opened");
745 
746 	FileInMemoryManager_fclose (me.get(), f2);
747 	Melder_assert (my openFiles -> size == 1);
748 	MelderInfo_writeLine (U"\tClosed file ", file2 -> path);
749 
750 	// read from open text file
751 
752 	MelderInfo_writeLine (U"\tRead as text file in memory: ", file1 -> path);
753 	char buf0 [200], buf1 [200];
754 	const long nbuf = 200;
755 
756 	FileInMemory fim = (FileInMemory) my files -> at [openFilesIndex1];
757 	FILE *file0 = fopen (Melder_peek32to8 (file1 -> path), "r");
758 	for (integer i = 0; i <= 2; i ++) {
759 		char *p0 = fgets (buf0, nbuf, file0);
760 		const integer pos0 = ftell (file0);
761 		char *p1 = FileInMemoryManager_fgets (me.get(), buf1, nbuf, f1);
762 		const integer pos1 = FileInMemoryManager_ftell (me.get(), f1);
763 		Melder_assert (Melder_equ (Melder_peek8to32 (buf0), Melder_peek8to32 (buf1)));
764 		Melder_assert (pos0 == pos1);
765 		Melder_assert (p0 == buf0 && p1 == buf1);
766 		MelderInfo_writeLine (U"\t\tRead 1 line. Positions: ", pos0, U" and ", pos1);
767 	}
768 
769 	MelderInfo_writeLine (U"\t\tRead while at EOF, returns nullptr");
770 	char *shouldbenull = FileInMemoryManager_fgets (me.get(), buf1, nbuf, f1);
771 	Melder_assert (shouldbenull == nullptr);
772 
773 	MelderInfo_writeLine (U"\tFinished reading... rewind ");
774 
775 	// read as binary file
776 
777 	rewind (file0);
778 	FileInMemoryManager_rewind (me.get(), f1);
779 
780 	MelderInfo_writeLine (U"\tRead as binary file in memory: ", file1 -> path);
781 
782 	Melder_assert (fim -> d_position == 0);
783 	const integer count = 8;
784 	size_t nread0 = fread (buf0, 1, count, file0);
785 	size_t nread1 =  FileInMemoryManager_fread (me.get(), buf1, 1, count, f1);
786 	MelderInfo_writeLine (U"\t\tRead ", nread0, U" and ", nread1, U" bytes");
787 
788 	Melder_assert (nread0 == nread0);
789 	Melder_assert (fim -> d_position == count);
790 
791 	nread0 = fread (buf0, 1, count, file0);
792 	nread1 = FileInMemoryManager_fread (me.get(), buf1, 1, count, f1);
793 	MelderInfo_writeLine (U"\t\tRead ", nread0, U" and ", nread1, U" bytes");
794 	Melder_assert (nread0 == nread1);
795 
796 	const int eof0 = feof (file0);
797 	const int eof1 = FileInMemoryManager_feof (me.get(), f1);
798 	MelderInfo_writeLine (U"\tEOF ? ", eof0, U" and ", eof1);
799 
800 	Melder_assert (eof0 != 0 && eof1 != 0);
801 
802 	//  clean up
803 
804 	MelderFile_delete (file1);
805 	MelderFile_delete (file2);
806 
807 	MelderInfo_writeLine (U"test_FileInMemoryManager_io: OK");
808 }
809 
810 /* End of file FileInMemoryManager.cpp */
811