ARES-SDK/ARES/api/file.h.lsl

250 lines
8.0 KiB
Plaintext

/* =========================================================================
*
* Nanite Systems Advanced Research Encapsulation System
*
* Copyright (c) 2023 Nanite Systems Corporation
*
* =========================================================================
*
* FILE.H.LSL Header Component
*
* This program is covered under the terms of the ARES Software Copyright
* License, Section 3 (ASCL-iii). It may be redistributed or used as the
* basis of commercial, closed-source products so long as steps are taken
* to ensure proper attribution as defined in the text of the license.
*
* To see the full text of the ASCL, type 'help license' on any standard
* ARES distribution, or visit http://nanite-systems.com/ASCL for the
* current version.
*
* DISCLAIMER
*
* 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
* DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
* =========================================================================
*
*/
#ifndef _ARES_FILE_H_
#define _ARES_FILE_H_
#include <utils.lsl>
#define FILE_R 0
#define FILE_INS 1
#define FILE_OUTS 2
#define FILE_USER 3
#define FILE_NAME 4
#define FILE_OFFSET 5
#define FILE_LENGTH 6
#define FILE_UNIT 7
/*
NEW UNIFORM FILE ACCESS SYSTEM - ARES 0.4.4
replaces ARES/api/file.h.lsl
*/
/*
To use this script, call fopen() with the 'outs', 'ins', and 'user' from main()
When file contents are available, the program will receive:
SIGNAL_NOTIFY
message: PROGRAM_NAME + " file"
ins: matching the file_handle returned by fopen()
You can then call fread(file_handle) to get contents.
fread() has the following special return values:
JSON_FALSE: file does not exist or file is empty
JSON_TRUE: file was opened successfully and data will arrive soon
If fread() returns any other value, it is file data.
fread() reads files in buffer chunks of around 1024 bytes, or one line for notecards, and always advances to the next chunk afterward.
fread() will close the file automatically when it is done reading, at which point getjs(tasks_queue, [file_handle]) will return JSON_INVALID.
By default, this API will automatically try to resolve tasks for you when the file is closed. This is good for simple file readers but wrong if you intend to do more processing afterward. To suppress this behavior, set '_resolved = 0' before calling fopen(), and pass NULL_KEY for ins.
*/
#ifndef FILE_STEP_SIZE
// define this as a small integer for speed-ups (don't go too far, though)
#define FILE_STEP_SIZE 1
#endif
// fopen(output_stream, input_stream, user_key, file_name): returns file_handle
key fopen(key outs, key ins, key user, string filename) {
key q = llGenerateKey();
// send request to io daemon to create pipe and fetch file length:
file_open(q, filename);
// track file information and prevent job from sleeping during load process
string metadata = list2js(JSON_ARRAY, [
_resolved, // callback number for program that summoned us
ins, // original input stream (often used as a callback handle)
outs, // output stream
user, // user that initiated the request
filename, // filename being worked on
NOWHERE, // file offset (haven't started reading yet)
NOWHERE, // file length (loaded first)
"" // file unit (loaded with file length)
]);
task_begin(q, metadata);
// metadata is stored in the global JSON variable tasks_queue, and can be accessed later
_resolved = 0;
return q;
}
// fread(file_handle): returns JSON_TRUE if file opened successfully, JSON_FALSE if file empty/missing, otherwise file text
// note that it may return up to 1025 bytes for a perfectly full notecard line + linebreak, but will otherwise always return 1024 bytes or fewer
string fread(key q) {
// split apart file metadata into a list (smaller code)
list file = js2list(getjs(tasks_queue, [q]));
integer file_length = geti(file, FILE_LENGTH);
string file_unit = gets(file, FILE_UNIT);
string fn = gets(file, FILE_NAME);
// copy contents of LSD pipe into local variable:
string buffer;
pipe_read(q, buffer);
integer offset = geti(file, FILE_OFFSET);
_resolved = 0;
if(!~file_length) {
// on the first successful call we load the file length and unit
list stat_parts = split(buffer, " ");
file_length = (integer)gets(stat_parts, 0);
file_unit = gets(stat_parts, 1);
tasks_queue = setjs(setjs(tasks_queue,
[q, FILE_LENGTH], (string)file_length),
[q, FILE_UNIT], (string)file_unit);
// for notecards, file length is measured as the number of lines,
// NOT the actual character count
if(file_length > 0) {
// a length greater than 0 indicates the file exists
// start reading file
offset = 0;
integer read_length = FILE_STEP_SIZE;
if(file_unit == "b")
read_length *= FILE_PAGE_LENGTH;
if(read_length > file_length)
read_length = file_length;
file_read(q, fn, (string)offset + " " + (string)read_length);
tasks_queue = setjs(tasks_queue, [q, FILE_OFFSET], (string)offset);
buffer = JSON_TRUE;
} else {
// no file was found, or file was empty
file_close(q);
// job can now end:
// resolve_io(geti(file, FILE_R), getk(file, FILE_OUTS), getk(file, FILE_INS));
resolve_i(geti(file, FILE_R), getk(file, FILE_INS));
task_end(q);
buffer = JSON_FALSE;
}
} else {
// got length earlier, so this must be file data
// move forward to next page:
if(file_unit == "b")
offset += (FILE_PAGE_LENGTH * FILE_STEP_SIZE);
else
offset += (FILE_STEP_SIZE);
// is there more file?
if(offset < file_length) {
// record new offset:
tasks_queue = setjs(tasks_queue, [q, FILE_OFFSET], (string)offset);
// get next section:
#ifdef DEBUG
echo("file.h.lsl: loading offset " + (string)offset + "/" + (string)file_length);
#endif
integer read_length = FILE_STEP_SIZE;
if(file_unit == "b")
read_length *= FILE_PAGE_LENGTH;
if(read_length + offset > file_length)
read_length = file_length - offset;
file_read(q, fn, (string)offset + " " + (string)read_length);
} else {
// reached end of file
file_close(q);
// job can now end:
// resolve_io(geti(file, FILE_R), getk(file, FILE_OUTS), getk(file, FILE_INS));
resolve_i(geti(file, FILE_R), getk(file, FILE_INS));
// delete file handle:
task_end(q);
}
}
// send data to the program
return buffer;
}
// preview what fread() will return without actually initiating the next file request
string fpeek(key q) {
// split apart file metadata into a list (smaller code)
list file = js2list(getjs(tasks_queue, [q]));
integer file_length = geti(file, FILE_LENGTH);
// copy contents of LSD pipe into local variable:
string buffer = llLinksetDataRead("p:" + (string)q);
if(!~file_length) {
// on the first successful call we load the file length
file_length = (integer)buffer;
// tasks_queue = setjs(tasks_queue, [q, FILE_LENGTH], (string)file_length);
// for notecards, file length is measured as 256 * (number of lines),
// NOT the actual character count
if(file_length > 0) {
// a length greater than 0 indicates the file exists
buffer = JSON_TRUE;
} else {
// no file was found, or file was empty
buffer = JSON_FALSE;
}
}
// send data to the program
return buffer;
}
// abort file reading (only needed when using fpeek()):
fclose(key q) {
// split apart file metadata into a list (smaller code)
list file = js2list(getjs(tasks_queue, [q]));
file_close(q);
// job can now end:
// resolve_io(geti(file, FILE_R), getk(file, FILE_OUTS), getk(file, FILE_INS));
resolve_i(geti(file, FILE_R), getk(file, FILE_INS));
// delete file handle:
task_end(q);
}
#endif // _ARES_FILE_H_