/* Copyright (C) 2020 falkTX This work is free. You can redistribute it and/or modify it under the terms of the Do What The Fuck You Want To Public License, Version 2, as published by Sam Hocevar. See the COPYING file for more details. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #define PLUGIN_URI "https://git.kx.studio/falkTX/lv2-state-test" #ifdef DO_NOT_HAVE_LV2_STATE_FREE_PATH // forwards compatibility with old lv2 headers #define LV2_STATE__freePath LV2_STATE_PREFIX "freePath" typedef void* LV2_State_Free_Path_Handle; typedef struct { LV2_State_Free_Path_Handle handle; void (*free_path)(LV2_State_Free_Path_Handle handle, char* path); } LV2_State_Free_Path; #endif typedef struct { bool activated, deactivated, saved, restored; LV2_Log_Logger logger; const LV2_Atom_Sequence* seqIn; LV2_Atom_Sequence* seqOut; const LV2_State_Free_Path* freePath; const LV2_State_Make_Path* makePath; const LV2_State_Map_Path* mapPath; char* randomNumStr; LV2_URID numberURID, atomStringURID; } StateTest; static void lv2_free_path(const LV2_State_Free_Path* freePath, char* path) { if (freePath != NULL) freePath->free_path(freePath->handle, path); else free(path); } static void create_paths(const LV2_State_Make_Path* const makePath, const LV2_State_Free_Path* const freePath, LV2_Log_Logger* const logger, const char* const randomNumStr, const bool instantiate) { char* path; int status; FILE* fd; // test getting initial dir path = makePath->path(makePath->handle, "."); if (path != NULL) { status = access(path, F_OK); lv2_log_note(logger, "state-test %s, host has makePath and initial path is: '%s' (access = %i)\n", instantiate ? "init" : "save", path, status); lv2_free_path(freePath, path); } else { lv2_log_warning(logger, "state-test %s, host has makePath but failed to get initial path\n", instantiate ? "init" : "save"); } // test creating single file path = makePath->path(makePath->handle, "single-file.txt"); if (path != NULL) { status = access(path, F_OK); lv2_log_note(logger, "state-test %s, request for single-file.txt path resulted in '%s' (access = %i)\n", instantiate ? "init" : "save", path, status); fd = fopen(path, "w"); if (fd != NULL) { fputs(randomNumStr, fd); fputs("\n", fd); fclose(fd); lv2_log_note(logger, "state-test %s, wrote '%s' to single-file.txt successfully\n", instantiate ? "init" : "save", randomNumStr); } else { lv2_log_error(logger, "state-test %s, failed to open single-file.txt for writing\n", instantiate ? "init" : "save"); } lv2_free_path(freePath, path); } else { lv2_log_error(logger, "state-test %s, request for single-file.txt path failed\n", instantiate ? "init" : "save"); } // test creating subdirs path = makePath->path(makePath->handle, "subdir1/subdir2/subdir3/subdir-file.txt"); if (path != NULL) { status = access(path, F_OK); lv2_log_note(logger, "state-test %s, request for subdir-file.txt path resulted in '%s' (access = %i)\n", instantiate ? "init" : "save", path, status); fd = fopen(path, "w"); if (fd != NULL) { fputs(randomNumStr, fd); fputs("\n", fd); fclose(fd); lv2_log_note(logger, "state-test %s, wrote '%s' to subdir-file.txt successfully\n", instantiate ? "init" : "save", randomNumStr); } else { lv2_log_error(logger, "state-test %s, failed to open subdir-file.txt for writing\n", instantiate ? "init" : "save"); } lv2_free_path(freePath, path); } else { lv2_log_error(logger, "state-test %s, request for subdir-file.txt path failed\n", instantiate ? "init" : "save"); } } static LV2_Handle instantiate(const LV2_Descriptor* const descriptor, const double sample_rate, const char* const bundle_path, const LV2_Feature* const* const features) { const LV2_State_Free_Path* freePath = NULL; const LV2_State_Make_Path* makePath = NULL; const LV2_State_Map_Path* mapPath = NULL; LV2_Log_Log* log = NULL; LV2_URID_Map* uridMap = NULL; for (int i=0; features[i] != NULL; ++i) { /**/ if (strcmp(features[i]->URI, LV2_STATE__freePath) == 0) freePath = features[i]->data; else if (strcmp(features[i]->URI, LV2_STATE__makePath) == 0) makePath = features[i]->data; else if (strcmp(features[i]->URI, LV2_STATE__mapPath) == 0) mapPath = features[i]->data; else if (strcmp(features[i]->URI, LV2_LOG__log) == 0) log = features[i]->data; else if (strcmp(features[i]->URI, LV2_URID__map) == 0) uridMap = features[i]->data; } if (uridMap == NULL) { fprintf(stderr, "uridMap feature missing\n"); return NULL; } StateTest* instance = calloc(1, sizeof(StateTest)); instance->freePath = freePath; instance->makePath = makePath; instance->mapPath = mapPath; lv2_log_logger_init(&instance->logger, uridMap, log); srand(time(NULL)); srand((int)(uintptr_t)instance); asprintf(&instance->randomNumStr, "%d", rand()); instance->atomStringURID = uridMap->map(uridMap->handle, LV2_ATOM__String); instance->numberURID = uridMap->map(uridMap->handle, PLUGIN_URI "#number"); if (makePath != NULL) create_paths(makePath, freePath, &instance->logger, instance->randomNumStr, true); else lv2_log_note(&instance->logger, "state-test init, host does not have makePath\n"); return instance; // unused (void)descriptor; (void)sample_rate; (void)bundle_path; } #define instancePtr ((StateTest*)instance) static void connect_port(LV2_Handle instance, uint32_t port, void* data_location) { switch (port) { case 0: instancePtr->seqIn = data_location; break; case 1: instancePtr->seqOut = data_location; break; } } static void activate(LV2_Handle instance) { instancePtr->activated = true; } static void run(LV2_Handle instance, uint32_t sample_count) { if (instancePtr->deactivated) { instancePtr->deactivated = false; lv2_log_note(&instancePtr->logger, "plugin was deactivated\n"); } if (instancePtr->activated) { instancePtr->activated = false; lv2_log_note(&instancePtr->logger, "plugin was activated\n"); } if (instancePtr->restored) { instancePtr->restored = false; lv2_log_note(&instancePtr->logger, "plugin state was restored\n"); } if (instancePtr->saved) { instancePtr->saved = false; lv2_log_note(&instancePtr->logger, "plugin state was saved\n"); } return; // unused (void)sample_count; } static void deactivate(LV2_Handle instance) { instancePtr->deactivated = true; } static void cleanup(LV2_Handle instance) { free(instance); } static LV2_State_Status save(LV2_Handle instance, LV2_State_Store_Function store, LV2_State_Handle handle, uint32_t flags, const LV2_Feature* const* features) { const LV2_State_Free_Path* freePath = NULL; const LV2_State_Make_Path* makePath = NULL; for (int i=0; features[i] != NULL; ++i) { /**/ if (freePath == NULL && strcmp(features[i]->URI, LV2_STATE__freePath) == 0) freePath = features[i]->data; else if (makePath == NULL && strcmp(features[i]->URI, LV2_STATE__makePath) == 0) makePath = features[i]->data; } create_paths(makePath, freePath, &instancePtr->logger, instancePtr->randomNumStr, false); store(handle, instancePtr->numberURID, instancePtr->randomNumStr, strlen(instancePtr->randomNumStr)+1, instancePtr->atomStringURID, LV2_STATE_IS_POD|LV2_STATE_IS_PORTABLE); instancePtr->saved = true; return LV2_STATE_SUCCESS; // TODO (void)flags; } static LV2_State_Status restore(LV2_Handle instance, LV2_State_Retrieve_Function retrieve, LV2_State_Handle handle, uint32_t flags, const LV2_Feature* const* features) { const LV2_State_Free_Path* freePath = NULL; const LV2_State_Make_Path* makePath = NULL; for (int i=0; features[i] != NULL; ++i) { /**/ if (freePath == NULL && strcmp(features[i]->URI, LV2_STATE__freePath) == 0) freePath = features[i]->data; else if (makePath == NULL && strcmp(features[i]->URI, LV2_STATE__makePath) == 0) makePath = features[i]->data; } if (makePath == NULL) return LV2_STATE_ERR_NO_FEATURE; char* const path = makePath->path(makePath->handle, "."); if (path != NULL) { lv2_log_note(&instancePtr->logger, "state-test restore ok, path is: '%s'\n", path); if (freePath != NULL) freePath->free_path(freePath->handle, path); else free(path); } else { lv2_log_note(&instancePtr->logger, "state-test restore ok, but failed to get initial path\n"); } // TODO restore previously saved randomNumStr instancePtr->restored = true; return LV2_STATE_SUCCESS; // TODO (void)retrieve; (void)handle; (void)flags; } static const void* extension_data(const char* const uri) { static const LV2_State_Interface iface = { .save = save, .restore = restore }; if (strcmp(uri, LV2_STATE__interface) == 0) return &iface; return NULL; } LV2_SYMBOL_EXPORT const LV2_Descriptor* lv2_descriptor(const uint32_t index) { static const LV2_Descriptor desc = { .URI = PLUGIN_URI, .instantiate = instantiate, .connect_port = connect_port, .activate = activate, .run = run, .deactivate = deactivate, .cleanup = cleanup, .extension_data = extension_data }; return index == 0 ? &desc : NULL; }