Update for latest LV2 persist extension.
[ardour.git] / libs / ardour / rdff.c
1 /*
2   RDFF - RDF in RIFF
3   Copyright 2011 David Robillard <http://drobilla.net>
4
5   Redistribution and use in source and binary forms, with or without
6   modification, are permitted provided that the following conditions are met:
7
8   1. Redistributions of source code must retain the above copyright notice,
9      this list of conditions and the following disclaimer.
10
11   2. Redistributions in binary form must reproduce the above copyright
12      notice, this list of conditions and the following disclaimer in the
13      documentation and/or other materials provided with the distribution.
14
15   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
16   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
17   AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
18   AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
19   OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20   SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21   INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22   CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
24   THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27 #include <assert.h>
28 #include <errno.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32
33 #include "rdff.h"
34
35 #define CHUNK_ID_LEN 4
36
37 static const char FILE_TYPE[CHUNK_ID_LEN]  = "RDFF";  /* RDFF File ID */
38 static const char CHUNK_TRIP[CHUNK_ID_LEN] = "trip";  /* Triple Chunk ID */
39 static const char CHUNK_URID[CHUNK_ID_LEN] = "urid";  /* URI-ID Chunk ID*/
40
41 struct _RDFF {
42         FILE*    fd;
43         uint32_t size;
44         bool     write;
45 };
46
47 RDFF
48 rdff_open(const char* path, bool write)
49 {
50         FILE* fd = fopen(path, (write ? "w" : "r"));
51         if (!fd) {
52                 fprintf(stderr, "%s\n", strerror(errno));
53                 return NULL;
54         }
55
56         uint32_t size = 0;
57
58         if (write) {
59                 fwrite("RIFF", CHUNK_ID_LEN, 1, fd);    /* RIFF chunk ID */
60                 fwrite(&size, sizeof(size), 1, fd);     /* RIFF chunk size */
61                 fwrite(FILE_TYPE, CHUNK_ID_LEN, 1, fd); /* File type */
62         } else {
63                 char magic[CHUNK_ID_LEN];
64                 if (fread(magic, CHUNK_ID_LEN, 1, fd) != 1
65                     || strncmp(magic, "RIFF", CHUNK_ID_LEN)) {
66                         fclose(fd);
67                         fprintf(stderr, "%s: error: not a RIFF file\n", path);
68                         return NULL;
69                 }
70
71                 if (fread(&size, sizeof(size), 1, fd) != 1) {
72                         fclose(fd);
73                         fprintf(stderr, "%s: error: missing RIFF chunk size\n", path);
74                         return NULL;
75                 }
76
77                 if (fread(magic, CHUNK_ID_LEN, 1, fd) != 1
78                     || strncmp(magic, FILE_TYPE, CHUNK_ID_LEN)) {
79                         fclose(fd);
80                         fprintf(stderr, "%s: error: not an %s RIFF file\n",
81                                 FILE_TYPE, path);
82                         return NULL;
83                 }
84         }
85
86         RDFF ret = (RDFF)malloc(sizeof(struct _RDFF));
87         ret->fd    = fd;
88         ret->size  = size;
89         ret->write = write;
90         return ret;
91 }
92
93 #define WRITE(ptr, size, nmemb, stream) \
94         if (fwrite(ptr, size, nmemb, stream) != nmemb) { \
95                 return RDFF_STATUS_UNKNOWN_ERROR; \
96         }
97
98 RDFFStatus
99 rdff_write_uri(RDFF        file,
100                uint32_t    id,
101                uint32_t    len,
102                const char* uri)
103 {
104         const uint32_t chunk_size = sizeof(id) + len + 1;
105         WRITE(CHUNK_URID,  CHUNK_ID_LEN,       1, file->fd);
106         WRITE(&chunk_size, sizeof(chunk_size), 1, file->fd);
107         WRITE(&id,         sizeof(id),         1, file->fd);
108         WRITE(uri,         len + 1,            1, file->fd);
109         if ((chunk_size % 2)) {
110                 WRITE("", 1, 1, file->fd);  /* pad */
111         }
112         file->size += 8 + chunk_size;
113         return RDFF_STATUS_OK;
114 }
115
116 RDFFStatus
117 rdff_write_triple(RDFF        file,
118                   uint32_t    subject,
119                   uint32_t    predicate,
120                   uint32_t    object_type,
121                   uint32_t    object_size,
122                   const void* object)
123 {
124         const uint32_t chunk_size = sizeof(RDFFTripleChunk) + object_size;
125         WRITE(CHUNK_TRIP,   CHUNK_ID_LEN,        1, file->fd);
126         WRITE(&chunk_size,  sizeof(chunk_size),  1, file->fd);
127         WRITE(&subject,     sizeof(subject),     1, file->fd);
128         WRITE(&predicate,   sizeof(predicate),   1, file->fd);
129         WRITE(&object_type, sizeof(object_type), 1, file->fd);
130         WRITE(&object_size, sizeof(object_size), 1, file->fd);
131         WRITE(object,       object_size,         1, file->fd);
132         if ((object_size % 2)) {
133                 WRITE("", 1, 1, file->fd);  /* write pad */
134         }
135         file->size += 8 + chunk_size;
136         return RDFF_STATUS_OK;
137 }
138
139 RDFFStatus
140 rdff_read_chunk(RDFF        file,
141                 RDFFChunk** buf)
142 {
143         if (feof(file->fd))
144                 return RDFF_STATUS_EOF;
145
146 #define READ(ptr, size, nmemb, stream) \
147         if (fread(ptr, size, nmemb, stream) != nmemb) { \
148                 return RDFF_STATUS_CORRUPT; \
149         }
150
151         const uint32_t alloc_size = (*buf)->size;
152
153         READ((*buf)->type,  sizeof((*buf)->type), 1, file->fd);
154         READ(&(*buf)->size, sizeof((*buf)->size), 1, file->fd);
155         if ((*buf)->size > alloc_size) {
156                 *buf = realloc(*buf, sizeof(RDFFChunk) + (*buf)->size);
157         }
158         READ((*buf)->data, (*buf)->size, 1, file->fd);
159         if (((*buf)->size % 2)) {
160                 char pad;
161                 READ(&pad, 1, 1, file->fd);  /* skip pad */
162         }
163         return RDFF_STATUS_OK;
164 }
165
166 bool
167 rdff_chunk_is_uri(RDFFChunk* chunk)
168
169 {
170         return !strncmp(chunk->type, CHUNK_URID, CHUNK_ID_LEN);
171 }
172
173 bool
174 rdff_chunk_is_triple(RDFFChunk* chunk)
175 {
176         return !strncmp(chunk->type, CHUNK_TRIP, CHUNK_ID_LEN);
177 }
178
179 void
180 rdff_close(RDFF file)
181 {
182         if (file) {
183                 if (file->write) {
184                         fseek(file->fd, 4, SEEK_SET);
185                         if (fwrite(&file->size, sizeof(file->size), 1, file->fd) != 1) {
186                                 fprintf(stderr, "failed to write RIFF header size\n");
187                         }
188                 }
189                 fclose(file->fd);
190         }
191
192         free(file);
193 }
194
195 #ifdef STANDALONE
196 // Test program
197 int
198 main(int argc, char** argv)
199 {
200         if (argc != 2) {
201                 fprintf(stderr, "Usage: %s FILENAME\n", argv[0]);
202                 return 1;
203         }
204
205         const char* const filename = argv[1];
206
207         RDFF file = rdff_open(filename, true);
208         if (!file)
209                 goto fail;
210
211         static const int N_URIS    = 16;
212         static const int N_RECORDS = 16;
213
214         char uri[64];
215         for (int i = 0; i < N_URIS; ++i) {
216                 snprintf(uri, sizeof(uri), "http://example.org/uri%02d", i + 1);
217                 rdff_write_uri(file, i + 1, strlen(uri), uri);
218         }
219
220         char val[6];
221         for (int i = 0; i < N_RECORDS; ++i) {
222                 snprintf(val, sizeof(val), "VAL%02d", i);
223                 rdff_write_triple(file,
224                                   0,
225                                   rand() % N_URIS,
226                                   0,
227                                   sizeof(val),
228                                   val);
229         }
230
231         rdff_close(file);
232
233         file = rdff_open(filename, false);
234         if (!file)
235                 goto fail;
236
237         RDFFChunk* chunk = malloc(sizeof(RDFFChunk));
238         chunk->size = 0;
239         for (int i = 0; i < N_URIS; ++i) {
240                 if (rdff_read_chunk(file, &chunk)
241                     || strncmp(chunk->type, CHUNK_URID, 4)) {
242                         fprintf(stderr, "error: expected %s chunk\n", CHUNK_URID);
243                         goto fail;
244                 }
245                 RDFFURIChunk* body = (RDFFURIChunk*)chunk->data;
246                 printf("URI: %s\n", body->uri);
247         }
248
249         for (int i = 0; i < N_RECORDS; ++i) {
250                 if (rdff_read_chunk(file, &chunk)
251                     || strncmp(chunk->type, CHUNK_TRIP, 4)) {
252                         fprintf(stderr, "error: expected %s chunk\n", CHUNK_TRIP);
253                         goto fail;
254                 }
255                 RDFFTripleChunk* body = (RDFFTripleChunk*)chunk->data;
256                 printf("KEY %d = %s\n", body->predicate, body->object);
257         }
258
259         free(chunk);
260         rdff_close(file);
261
262         return 0;
263
264 fail:
265         rdff_close(file);
266         fprintf(stderr, "Test failed\n");
267         return 1;
268 }
269 #endif // STANDALONE