Metadata Example

// Demonstrates OMF metadata storage and retrieval.

#include <omf.h>
#include <stdio.h>
#include <stdlib.h>

static bool write(const char *path) {
    OmfError *error;
    OmfWriter *writer;
    OmfProject project;
    OmfHandle *proj_handle, *array_handle, *object_handle;

    // Open file.
    writer = omf_writer_open(path);
    // Create project and keep the handle to it.
    project = omf_project_init("metadata.omf");
    proj_handle = omf_writer_project(writer, &project);

    // Add a metadata value of each simple type. This is added directly to the project, but
    // an element or attribute handle will work too. This all gets stored as a chunk of
    // arbitrary JSON data in the file. Attaching too much meaning to metadata values may
    // make your file less useful in other applications as they won't necessarily know what
    // it means.
    //
    // Metadata keys, and values when they're strings, must be UTF-8 encoded. ASCII is also
    // acceptable because it's a subset of UTF-8.

    // Null values store only the key. This can be used where just the presence of the key is
    // useful or where a value isn't known.
    omf_writer_metadata_null(writer, proj_handle, "version");
    // Boolean values store true or false.
    omf_writer_metadata_boolean(writer, proj_handle, "is_draft", true);
    // Number values store a double value.
    omf_writer_metadata_number(writer, proj_handle, "importance", 2.6);
    // String value. This could also be used to store date or date/time values, which should
    // be in ISO 8601 format.
    omf_writer_metadata_string(writer, proj_handle, "source", "omf example code");

    // We can also store arrays of metadata values. Items in an array can have different types.
    // The same `omf_writer_metadata_*` functions are used to append array items, but the `key`
    // argument is ignored and should be null.
    array_handle = omf_writer_metadata_list(writer, proj_handle, "list");
    omf_writer_metadata_string(writer, array_handle, NULL, "first value");
    omf_writer_metadata_string(writer, array_handle, NULL, "second value");
    omf_writer_metadata_number(writer, array_handle, NULL, 3);

    // Finally we have object values, which contain their own key/value pairs. This is a good
    // way to group and label application-specific data for example.
    object_handle = omf_writer_metadata_object(writer, proj_handle, "my-company");
    omf_writer_metadata_string(
        writer, object_handle, "project-uuid", "550e8400-e29b-41d4-a716-446655440000");
    omf_writer_metadata_string(
        writer, object_handle, "project-uri", "https://example.com/");
    omf_writer_metadata_string(
        writer, object_handle, "project-revision", "1.4.2");

    // Finish writing and close the file.
    omf_writer_finish(writer, NULL);

    // Check for errors. The `omf_error` call will return the *first* error, even if several
    // functions failed since after detecting an invalid argument.
    if ((error = omf_error()) != NULL) {
        fprintf(stderr, "[write failed] %s (%d)\n", error->message, error->code);
        omf_error_free(error);
        return false;
    }
    return true;
}

static void print_indent(int indent) {
    int i;

    for (i = 0; i < indent; i++) {
        printf("    ");
    }
}

static void print_metadata_value(const OmfValue *value, int indent, bool is_array_item) {
    size_t i;

    print_indent(indent);
    if (!is_array_item) {
        printf("\"%s\": ", value->name);
    }
    // First check the value type.
    switch (value->type) {
    case OMF_VALUE_TYPE_NULL:
        // No valid fields for null values.
        printf("null,\n");
        break;
    case OMF_VALUE_TYPE_BOOLEAN:
        // `boolean` field is valid.
        printf("%s,\n", value->boolean ? "true" : "false");
        break;
    case OMF_VALUE_TYPE_NUMBER:
        // `number` field is valid.
        printf("%g,\n", value->number);
        break;
    case OMF_VALUE_TYPE_STRING:
        // `string` field is valid.
        printf("\"%s\",\n", value->string);
        break;
    case OMF_VALUE_TYPE_LIST:
        // `values` and `n_values` fields are valid and contain ordered values.
        printf("[\n");
        for (i = 0; i < value->n_values; i++) {
            print_metadata_value(&value->values[i], indent + 1, true);
        }
        print_indent(indent);
        printf("],\n");
        break;
    default: // OMF_VALUE_TYPE_OBJECT
        // `values` and `n_values` fields are valid and contain named values.
        printf("{\n");
        for (i = 0; i < value->n_values; i++) {
            print_metadata_value(&value->values[i], indent + 1, false);
        }
        print_indent(indent);
        printf("},\n");
        break;
    }
}

static bool read(const char *path) {
    OmfReader *reader;
    OmfError *error;
    const OmfProject *project;
    size_t i;

    // Open the file.
    reader = omf_reader_open(path);
    // Read the project.
    project = omf_reader_project(reader, NULL);
    if (!project) {
        error = omf_error();
        fprintf(stderr, "[read failed] %s (%d)\n", error->message, error->code);
        omf_error_free(error);
        return false;
    }
    // Print project contents.
    printf("name: %s\n", project->name);

    // Metadata is stored as a list of `OmfValue` structs in `project->metadata` with length
    // `project->n_metadata`. The order that values were written in is not preserved.
    // Inside `OmfValue` the `type` field stores the type of the value and defines which other
    // fields are valid. Unused fields will be zeroed.
    //
    // The `OmfElement` and `OmfAttribute` fields have matching metadata fields.
    printf("metadata: {\n");
    for (i = 0; i < project->n_metadata; i++) {
        print_metadata_value(&project->metadata[i], 1, false);
    }
    printf("}\n");

    // Close the reader only once we're done with `project`.
    omf_reader_close(reader);

    // Check for errors.
    if ((error = omf_error()) != NULL) {
        fprintf(stderr, "[read failed] %s (%d)\n", error->message,
                error->code);
        omf_error_free(error);
        return false;
    }
    return true;
}

int main() {
    if (!write("metadata.omf")) return 1;
    if (!read("metadata.omf")) return 1;
    return 0;
}