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