diff options
Diffstat (limited to 'Src/replicant/nswasabi')
31 files changed, 4890 insertions, 0 deletions
diff --git a/Src/replicant/nswasabi/APEv2Metadata.cpp b/Src/replicant/nswasabi/APEv2Metadata.cpp new file mode 100644 index 00000000..979a987a --- /dev/null +++ b/Src/replicant/nswasabi/APEv2Metadata.cpp @@ -0,0 +1,306 @@ +#include "APEv2Metadata.h" +#include "metadata/MetadataKeys.h" +#include "nu/ByteReader.h" +#include "nswasabi/ReferenceCounted.h" +#include <stdlib.h> +#include <stdio.h> + +static inline bool TestFlag(int flags, int flag_to_check) +{ + if (flags & flag_to_check) + return true; + return false; +} + +api_metadata *APEv2Metadata::metadata_api=0; + +APEv2Metadata::APEv2Metadata() +{ + apev2_tag=0; +} + +APEv2Metadata::~APEv2Metadata() +{ +} + +int APEv2Metadata::Initialize(api_metadata *metadata_api) +{ + APEv2Metadata::metadata_api = metadata_api; + return NErr_Success; +} + +int APEv2Metadata::Initialize(nsapev2_tag_t tag) +{ + apev2_tag = tag; + return NErr_Success; +} + + +/* ifc_metadata implementation */ +int APEv2Metadata::Metadata_GetField(int field, unsigned int index, nx_string_t *value) +{ + if (!apev2_tag) + return NErr_Unknown; + + switch (field) + { + case MetadataKeys::TRACK_GAIN: + return NSAPEv2_Tag_GetString(apev2_tag, "REPLAYGAIN_TRACK_GAIN", index, value); + case MetadataKeys::TRACK_PEAK: + return NSAPEv2_Tag_GetString(apev2_tag, "REPLAYGAIN_TRACK_PEAK", index, value); + case MetadataKeys::ALBUM_GAIN: + return NSAPEv2_Tag_GetString(apev2_tag, "REPLAYGAIN_ALBUM_GAIN", index, value); + case MetadataKeys::ALBUM_PEAK: + return NSAPEv2_Tag_GetString(apev2_tag, "REPLAYGAIN_ALBUM_PEAK", index, value); + } + + return NErr_Unknown; +} + +int APEv2Metadata::Metadata_GetInteger(int field, unsigned int index, int64_t *value) +{ + if (!apev2_tag) + return NErr_Unknown; + + return NErr_Unknown; +} + +int APEv2Metadata::Metadata_GetReal(int field, unsigned int index, double *value) +{ + if (!apev2_tag) + return NErr_Unknown; + + int ret; + nx_string_t str; + switch (field) + { + case MetadataKeys::TRACK_GAIN: + ret = NSAPEv2_Tag_GetString(apev2_tag, "REPLAYGAIN_TRACK_GAIN", index, &str); + if (ret == NErr_Success) + { + ret = NXStringGetDoubleValue(str, value); + NXStringRelease(str); + } + return ret; + case MetadataKeys::TRACK_PEAK: + ret = NSAPEv2_Tag_GetString(apev2_tag, "REPLAYGAIN_TRACK_PEAK", index, &str); + if (ret == NErr_Success) + { + ret = NXStringGetDoubleValue(str, value); + NXStringRelease(str); + } + return ret; + case MetadataKeys::ALBUM_GAIN: + ret = NSAPEv2_Tag_GetString(apev2_tag, "REPLAYGAIN_ALBUM_GAIN", index, &str); + if (ret == NErr_Success) + { + ret = NXStringGetDoubleValue(str, value); + NXStringRelease(str); + } + return ret; + case MetadataKeys::ALBUM_PEAK: + ret = NSAPEv2_Tag_GetString(apev2_tag, "REPLAYGAIN_ALBUM_PEAK", index, &str); + if (ret == NErr_Success) + { + ret = NXStringGetDoubleValue(str, value); + NXStringRelease(str); + } + return ret; + } + return NErr_Unknown; +} + +#ifdef _WIN32 +#define strcasecmp _stricmp +#endif + +static const char *APEv2_GetMIME(const char *extension) +{ + if (!extension) + return 0; + + if (strcasecmp(extension, "JPG") == 0 || strcasecmp(extension, "JPEG") == 0) + return "image/jpeg"; + if (strcasecmp(extension, "PNG") == 0) + return "image/png"; + if (strcasecmp(extension, "GIF") == 0) + return "image/gif"; + if (strcasecmp(extension, "BMP") == 0) + return "image/bmp"; + + return 0; +} + +static int APEv2_ParseArt(const void *bytes, size_t length, artwork_t *out_data, data_flags_t flags) +{ + if (out_data) + { + nx_data_t data=0; + if (flags != DATA_FLAG_NONE) + { + bytereader_s byte_reader; + bytereader_init(&byte_reader, bytes, length); + if (bytereader_size(&byte_reader) == 0) + return NErr_Insufficient; + + const char *description_start = (const char *)bytereader_pointer(&byte_reader); + const char *extension_start=0; + uint8_t byte; + do + { + if (bytereader_size(&byte_reader) == 0) + return NErr_Insufficient; + byte = bytereader_read_u8(&byte_reader); + if (byte == '.') // found extension + { + extension_start = (const char *)bytereader_pointer(&byte_reader); + } + } while (byte && bytereader_size(&byte_reader)); + + size_t length = bytereader_size(&byte_reader); + + if (length == 0) + return NErr_Empty; + + if (TestFlag(flags, DATA_FLAG_DATA)) + { + int ret = NXDataCreate(&data, bytereader_pointer(&byte_reader), length); + if (ret != NErr_Success) + return ret; + } + else + { + int ret = NXDataCreateEmpty(&data); + if (ret != NErr_Success) + return ret; + } + + if (TestFlag(flags, DATA_FLAG_DESCRIPTION)) + { + ReferenceCountedNXString description; + size_t length; + if (extension_start) + length = (size_t)extension_start - (size_t)description_start - 1; + else + length = (size_t)bytereader_pointer(&byte_reader) - (size_t)description_start - 1; + + if (length) + { + int ret = NXStringCreateWithBytes(&description, description_start, length, nx_charset_utf8); + if (ret != NErr_Success) + { + NXDataRelease(data); + return ret; + } + NXDataSetDescription(data, description); + } + } + + if (TestFlag(flags, DATA_FLAG_MIME)) + { + ReferenceCountedNXString mime_type; + const char *mime_string = APEv2_GetMIME(extension_start); + if (mime_string) + { + int ret = NXStringCreateWithUTF8(&mime_type, mime_string); + if (ret != NErr_Success) + { + NXDataRelease(data); + return ret; + } + } + } + } + out_data->data = data; + /* we don't know these */ + out_data->height=0; + out_data->width=0; + } + return NErr_Success; +} + +int APEv2Metadata::Metadata_GetArtwork(int field, unsigned int index, artwork_t *data, data_flags_t flags) +{ + if (!apev2_tag) + return NErr_Unknown; + + int ret; + const void *bytes; + size_t length; + switch(field) + { + case MetadataKeys::ALBUM: + ret = NSAPEv2_Tag_GetBinary(apev2_tag, "Cover Art (front)", index, &bytes, &length); + if (ret == NErr_Success) + return APEv2_ParseArt(bytes, length, data, flags); + return ret; + } + return NErr_Unknown; +} + +int APEv2Metadata::MetadataEditor_SetField(int field, unsigned int index, nx_string_t value) +{ + switch (field) + { + case MetadataKeys::TRACK_GAIN: + return NSAPEv2_Tag_SetString(apev2_tag, "REPLAYGAIN_TRACK_GAIN", index, value); + case MetadataKeys::TRACK_PEAK: + return NSAPEv2_Tag_SetString(apev2_tag, "REPLAYGAIN_TRACK_PEAK", index, value); + case MetadataKeys::ALBUM_GAIN: + return NSAPEv2_Tag_SetString(apev2_tag, "REPLAYGAIN_ALBUM_GAIN", index, value); + case MetadataKeys::ALBUM_PEAK: + return NSAPEv2_Tag_SetString(apev2_tag, "REPLAYGAIN_ALBUM_PEAK", index, value); + } + return NErr_Unknown; +} + +int APEv2Metadata::MetadataEditor_SetInteger(int field, unsigned int index, int64_t value) +{ + return NErr_Unknown; +} + +int APEv2Metadata::MetadataEditor_SetReal(int field, unsigned int index, double value) +{ + // TODO: but we need NXStringCreateFromDouble which I don't feel like writing right now + return NErr_Unknown; +} + +static void APEv2_GetFilenameForMIME(char *filename, const char *type, nx_string_t mime_type) +{ + if (mime_type) + { + if (NXStringKeywordCompareWithCString(mime_type, "image/jpeg") == NErr_True || NXStringKeywordCompareWithCString(mime_type, "image/jpg") == NErr_True) + sprintf(filename, "%s.jpeg", type); + else if (NXStringKeywordCompareWithCString(mime_type, "image/png") == NErr_True) + sprintf(filename, "%s.png", type); + if (NXStringKeywordCompareWithCString(mime_type, "image/gif") == NErr_True) + sprintf(filename, "%s.gif", type); + if (NXStringKeywordCompareWithCString(mime_type, "image/bmp") == NErr_True) + sprintf(filename, "%s.bmp", type); + else + sprintf(filename, "%s.jpg", type); // TODO: perhaps we could use whatever is after image/ + } + else + sprintf(filename, "%s.jpg", type); // ehh, just guess +} + +int APEv2Metadata::MetadataEditor_SetArtwork(int field, unsigned int index, artwork_t *data, data_flags_t flags) +{ + ReferenceCountedNXString mime_type; + const void *bytes = 0; + size_t length = 0; + switch(field) + { + case MetadataKeys::ALBUM: + if (data && NXDataGet(data->data, &bytes, &length) == NErr_Success) + { + char filename[256] = {0}; + NXDataGetMIME(data->data, &mime_type); + APEv2_GetFilenameForMIME(filename, "cover", mime_type); /* TODO: perhaps use description, instead? */ + return NSAPEv2_Tag_SetArtwork(apev2_tag, "Cover Art (front)", index, filename, bytes, length); + } + else + return NSAPEv2_Tag_SetArtwork(apev2_tag, "Cover Art (front)", index, 0, 0, 0); + } + return NErr_Unknown; +} diff --git a/Src/replicant/nswasabi/APEv2Metadata.h b/Src/replicant/nswasabi/APEv2Metadata.h new file mode 100644 index 00000000..ba4e15ac --- /dev/null +++ b/Src/replicant/nswasabi/APEv2Metadata.h @@ -0,0 +1,29 @@ +#pragma once +#include "metadata/metadata.h" +#include "nsapev2/nsapev2.h" + +/* this class mimics ifc_metadata and ifc_metadata_editor, but doesn't inherit (because it's not given out directly) */ +class APEv2Metadata +{ +public: + APEv2Metadata(); + ~APEv2Metadata(); + + static int Initialize(api_metadata *metadata_api); + int Initialize(nsapev2_tag_t tag); + + /* ifc_metadata implementation */ + int WASABICALL Metadata_GetField(int field, unsigned int index, nx_string_t *value); + int WASABICALL Metadata_GetInteger(int field, unsigned int index, int64_t *value); + int WASABICALL Metadata_GetReal(int field, unsigned int index, double *value); + int WASABICALL Metadata_GetArtwork(int field, unsigned int index, artwork_t *artwork, data_flags_t flags); + + /* ifc_metadata_editor implementation */ + int WASABICALL MetadataEditor_SetField(int field, unsigned int index, nx_string_t value); + int WASABICALL MetadataEditor_SetInteger(int field, unsigned int index, int64_t value); + int WASABICALL MetadataEditor_SetReal(int field, unsigned int index, double value); + int WASABICALL MetadataEditor_SetArtwork(int field, unsigned int index, artwork_t *data, data_flags_t flags); +private: + nsapev2_tag_t apev2_tag; + static api_metadata *metadata_api; +}; diff --git a/Src/replicant/nswasabi/Android.mk b/Src/replicant/nswasabi/Android.mk new file mode 100644 index 00000000..39682221 --- /dev/null +++ b/Src/replicant/nswasabi/Android.mk @@ -0,0 +1,12 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE := nswasabi +LOCAL_ARM_MODE := arm +LOCAL_C_INCLUDES := $(ROOT_REPLICANT) +LOCAL_CFLAGS := -fvisibility=hidden +LOCAL_SRC_FILES := PlaybackBase.cpp XMLString.cpp ID3v2Metadata.cpp ID3v1Metadata.cpp APEv2Metadata.cpp ApplicationBase.cpp +LOCAL_STATIC_LIBRARIES := nu +LOCAL_EXPORT_LDLIBS := -llog +include $(BUILD_STATIC_LIBRARY) + diff --git a/Src/replicant/nswasabi/ApplicationBase.cpp b/Src/replicant/nswasabi/ApplicationBase.cpp new file mode 100644 index 00000000..334c3e24 --- /dev/null +++ b/Src/replicant/nswasabi/ApplicationBase.cpp @@ -0,0 +1,168 @@ +#include "ApplicationBase.h" +#include "foundation/error.h" +#include "application/features.h" +#include <stdio.h> // for sprintf +#ifdef __ANDROID__ +#include <android/log.h> +#endif + +ApplicationBase::ApplicationBase() +{ + data_path = 0; + all_permissions_enabled = false; + device_id = 0; +} + +ApplicationBase::~ApplicationBase() +{ + NXURIRelease(data_path); + data_path = 0; + NXStringRelease(device_id); + device_id = 0; +} + +int ApplicationBase::Initialize() +{ + return NErr_Success; +} + +/* and call this after doing your own shutdown */ +void ApplicationBase::Shutdown() +{ + NXURIRelease(data_path); + data_path = 0; +} + +void ApplicationBase::SetDataPath(nx_uri_t new_data_path) +{ + nx_uri_t old_path = data_path; + data_path = NXURIRetain(new_data_path); + NXURIRelease(old_path); +} + +void ApplicationBase::SetPermission(GUID feature) +{ + permissions.insert(feature); +} + +void ApplicationBase::RemovePermission(GUID permission) +{ + permissions.erase(permission); +} + +void ApplicationBase::EnableAllPermissions() +{ + all_permissions_enabled=true; +} + +void ApplicationBase::ClearPermissions() +{ + permissions.clear(); +} + +void ApplicationBase::NotifyPermissions(api_syscb *system_callbacks) +{ + if (system_callbacks) + system_callbacks->IssueCallback(Features::event_type, Features::permissions_changed); +} + +int ApplicationBase::Application_GetDataPath(nx_uri_t *path) +{ + *path=NXURIRetain(data_path); + if (data_path) + return NErr_Success; + else + return NErr_Empty; +} + +int ApplicationBase::Application_GetPermission(GUID feature) +{ + if (all_permissions_enabled) + return NErr_True; + else if (permissions.find(feature) == permissions.end()) + return NErr_False; + else + return NErr_True; +} + +int ApplicationBase::Application_GetFeature(GUID feature) +{ + if (features.find(feature) == features.end()) + return NErr_False; + else + return NErr_True; +} + +void ApplicationBase::Application_SetFeature(GUID feature) +{ + features.insert(feature); +} + +void ApplicationBase::SetDeviceID(nx_string_t device_id) +{ + nx_string_t old = this->device_id; + this->device_id = NXStringRetain(device_id); + NXStringRelease(old); +} + +static void GUIDtoCString(const GUID &guid, char *target) +{ + //{2E9CE2F8-E26D-4629-A3FF-5DF619136B2C} + sprintf(target, "{%08x-%04x-%04x-%02x%02x%02x%02x%02x%02x%02x%02x}", + (int)guid.Data1, (int)guid.Data2, (int)guid.Data3, + (int)guid.Data4[0], (int)guid.Data4[1], + (int)guid.Data4[2], (int)guid.Data4[3], + (int)guid.Data4[4], (int)guid.Data4[5], + (int)guid.Data4[6], (int)guid.Data4[7] ); + +} + +static const char *GetFeatureName(const GUID &guid) +{ + if (guid == Features::aac_playback) + return "AAC Playback"; + else if (guid == Features::gapless) + return "Gapless Playback"; + else if (guid == Features::flac_playback) + return "FLAC Playback"; + else if (guid == Features::gracenote_autotag) + return "Gracenote Autotagger"; + else + return 0; /* the lack of of return 0 by default here was why it was crashing */ + +} + +void ApplicationBase::DumpPermissions() +{ +#ifdef __ANDROID__ + char guid_string[64]; + FeatureList::iterator itr; + for (itr=features.begin();itr!=features.end();itr++) + { + GUIDtoCString(*itr, guid_string); + const char *feature_name = GetFeatureName(*itr); + if (feature_name) + __android_log_print(ANDROID_LOG_INFO, "libreplicant", "[Feature] %s (%s)", guid_string, feature_name); + else + __android_log_print(ANDROID_LOG_INFO, "libreplicant", "[Feature] %s", guid_string); + } + + for (itr=permissions.begin();itr!=permissions.end();itr++) + { + GUIDtoCString(*itr, guid_string); + const char *feature_name = GetFeatureName(*itr); + if (feature_name) + __android_log_print(ANDROID_LOG_INFO, "libreplicant", "[Permission] %s (%s)", guid_string, feature_name); + else + __android_log_print(ANDROID_LOG_INFO, "libreplicant", "[Permission] %s", guid_string); + } +#endif +} + +int ApplicationBase::Application_GetDeviceID(nx_string_t *value) +{ + if (!device_id) + return NErr_Empty; + *value = NXStringRetain(device_id); + return NErr_Success; +} diff --git a/Src/replicant/nswasabi/ApplicationBase.h b/Src/replicant/nswasabi/ApplicationBase.h new file mode 100644 index 00000000..f82f0b3a --- /dev/null +++ b/Src/replicant/nswasabi/ApplicationBase.h @@ -0,0 +1,44 @@ +#pragma once +#include "application/api_application.h" +#include <set> +#include "syscb/api_syscb.h" + +/* implements non-platform-specific methods of api_application. +You can derive your Application implementation from this to ease your life */ +class ApplicationBase : public api_application +{ +public: + ApplicationBase(); + ~ApplicationBase(); + + /* call this (and check the return value) before doing your own initialization */ + int Initialize(); + + /* and call this after doing your own shutdown */ + void Shutdown(); + + void SetDataPath(nx_uri_t data_path); + void SetPermission(GUID feature); + void RemovePermission(GUID permission); + void SetDeviceID(nx_string_t device_id); + void EnableAllPermissions(); + void ClearPermissions(); + void NotifyPermissions(api_syscb *system_callbacks); /* pass in the syscb API to avoid a dependency */ + void DumpPermissions(); /* dumps permissions list to the log file */ +protected: + /* api_application implementation */ + int Application_GetDataPath(nx_uri_t *path); + int Application_GetPermission(GUID feature); + int Application_GetFeature(GUID feature); + void Application_SetFeature(GUID feature); + int Application_GetDeviceID(nx_string_t *value); + +private: + typedef std::set<GUID> FeatureList; + FeatureList features; + FeatureList permissions; + bool all_permissions_enabled; /* bypass for developer/QA testing */ + nx_uri_t data_path; + nx_string_t device_id; + +}; diff --git a/Src/replicant/nswasabi/AutoCharNX.h b/Src/replicant/nswasabi/AutoCharNX.h new file mode 100644 index 00000000..86e83b84 --- /dev/null +++ b/Src/replicant/nswasabi/AutoCharNX.h @@ -0,0 +1,184 @@ +#pragma once +#include "../nx/nxstring.h" +#include "../nx/nxuri.h" +#include "../foundation/error.h" +#include <stdlib.h> + +template <nx_charset_t charset> +class AutoCharNX +{ +public: + AutoCharNX() + { + Init(); + } + + AutoCharNX(size_t bytes) + { + Init(); + ptr = (char *)malloc(bytes); + malloc_size = bytes; + } + + AutoCharNX(nx_string_t string) + { + Init(); + + Set(string); + } + + AutoCharNX(nx_uri_t filename) + { + Init(); + + Set(filename); + } + + ~AutoCharNX() + { + if (owned) + free(ptr); + if (reference_string) + NXStringRelease(reference_string); + } + + int Set(nx_string_t string) + { + if (reference_string == string) + return NErr_Success; + + if (reference_string) + NXStringRelease(reference_string); + reference_string=0; + + size_t byte_count=0; + int ret = NXStringGetBytesSize(&byte_count, string, charset, nx_string_get_bytes_size_null_terminate); + if(ret == NErr_DirectPointer) + { + if (owned) + { + free(ptr); + ptr=0; + length=0; + malloc_size=0; + } + ret = NXStringGetBytesDirect((const void **)&ptr, &length, string, charset, nx_string_get_bytes_size_null_terminate); + reference_string = NXStringRetain(string); + owned=false; + } + else if (ret == NErr_Success) + { + if (owned) + { + if (byte_count > malloc_size) + { + ptr = (char *)realloc(ptr, byte_count); + malloc_size = byte_count; + } + } + else + { + /* not owned. need to allocate */ + ptr = (char *)malloc(byte_count); + malloc_size = byte_count; + owned=true; + } + + if (ptr) + { + ret = NXStringGetBytes(&length, string, ptr, byte_count, charset, nx_string_get_bytes_size_null_terminate); + } + else + { + return NErr_OutOfMemory; + } + } + else + { + Clear(); + } + return ret; + } + + int Set(nx_uri_t filename) + { + int ret; + nx_string_t string; + ret = NXURIGetNXString(&string, filename); + if (ret == NErr_Success) + { + ret = Set(string); + NXStringRelease(string); + } + else + { + Clear(); + // failed! we need to clean up + } + return ret; + } + + operator const char *() const + { + if (length) + return ptr; + else + return 0; + } + + /* this one will never return a NULL, always a valid string */ + const char *GetValidString() const + { + if (length) + return ptr; + else + return ""; + } + + /* the Clear function clears the string but doesn't deallocate memory */ + void Clear() + { + if (!owned) + ptr=0; + length=0; + + if (reference_string) + NXStringRelease(reference_string); + reference_string=0; + } + + size_t size() + { + if (length) + return length-1; + else + return 0; + } +private: + void Init() + { + ptr=0; + length=0; + owned=false; + reference_string=0; + malloc_size=0; + } + char *ptr; + size_t length; + size_t malloc_size; + bool owned; + nx_string_t reference_string; +}; + +typedef AutoCharNX<nx_charset_utf8> AutoCharUTF8; +#define AutoCharPrintfUTF8(x) (AutoCharUTF8(x).GetValidString()) +class AutoCharNative +{ +public: +}; + +class AutoFilename +{ +public: +}; + diff --git a/Src/replicant/nswasabi/ComponentManagerBase.cpp b/Src/replicant/nswasabi/ComponentManagerBase.cpp new file mode 100644 index 00000000..aa8d3f43 --- /dev/null +++ b/Src/replicant/nswasabi/ComponentManagerBase.cpp @@ -0,0 +1,145 @@ +#include "ComponentManagerBase.h" +#include "foundation/error.h" +#include "nx/nxuri.h" + +ComponentManagerBase::ComponentManagerBase() +{ + phase=PHASE_INITIALIZE; + service_api=0; + component_sync=0; +} + +int ComponentManagerBase::LateLoad(ifc_component *component) +{ + int ret; + + if (phase >= PHASE_REGISTERED) + { + ret = component->RegisterServices(service_api); + if (ret != NErr_Success) + { + int ret2 = component->Quit(service_api); + if (ret2 == NErr_TryAgain) + { + component_sync->Wait(1); + } + return ret; + } + } + + if (phase >= PHASE_LOADING) + { + ret = component->OnLoading(service_api); + if (ret != NErr_Success) + { + int ret2 = component->Quit(service_api); + if (ret2 == NErr_TryAgain) + { + component_sync->Wait(1); + } + return ret; + } + } + + if (phase >= PHASE_LOADED) + { + ret = component->OnLoaded(service_api); + if (ret != NErr_Success) + { + int ret2 = component->Quit(service_api); + if (ret2 == NErr_TryAgain) + { + component_sync->Wait(1); + } + return ret; + } + } + return NErr_Success; +} + +void ComponentManagerBase::SetServiceAPI(api_service *service_api) +{ + this->service_api = service_api; + service_api->QueryInterface(&component_sync); +} + +int ComponentManagerBase::Load() +{ + if (phase != PHASE_INITIALIZE) + return NErr_Error; + + int ret; + + /* RegisterServices phase */ + for (ComponentList::iterator itr=components.begin();itr!=components.end();) + { + ifc_component *component = *itr; + ComponentList::iterator next=itr; + next++; + ret = component->RegisterServices(service_api); + if (ret != NErr_Success) + { + int ret2 = component->Quit(service_api); + if (ret2 == NErr_TryAgain) + { + component_sync->Wait(1); + } + NXURIRelease(component->component_info.filename); + CloseComponent(component); + components.erase(component); + + } + itr=next; + } + + phase = PHASE_REGISTERED; + + /* OnLoading phase */ + for (ComponentList::iterator itr=components.begin();itr!=components.end();) + { + ifc_component *component = *itr; + ComponentList::iterator next=itr; + next++; + ret = component->OnLoading(service_api); + if (ret != NErr_Success) + { + int ret2 = component->Quit(service_api); + if (ret2 == NErr_TryAgain) + { + component_sync->Wait(1); + } + NXURIRelease(component->component_info.filename); + CloseComponent(component); + components.erase(component); + + } + itr=next; + } + + phase = PHASE_LOADING; + + /* OnLoaded phase */ + for (ComponentList::iterator itr=components.begin();itr!=components.end();) + { + ifc_component *component = *itr; + ComponentList::iterator next=itr; + next++; + ret = component->OnLoading(service_api); + if (ret != NErr_Success) + { + int ret2 = component->Quit(service_api); + if (ret2 == NErr_TryAgain) + { + component_sync->Wait(1); + } + NXURIRelease(component->component_info.filename); + CloseComponent(component); + components.erase(component); + } + itr=next; + } + + phase = PHASE_LOADED; + + return NErr_Success; +} diff --git a/Src/replicant/nswasabi/ComponentManagerBase.h b/Src/replicant/nswasabi/ComponentManagerBase.h new file mode 100644 index 00000000..00252b36 --- /dev/null +++ b/Src/replicant/nswasabi/ComponentManagerBase.h @@ -0,0 +1,31 @@ +#pragma once +#include "nx/nxuri.h" +#include "service/api_service.h" +#include "component/ifc_component.h" +#include "nu/PtrDeque.h" +#include "component/ifc_component_sync.h" + +class ComponentManagerBase +{ +public: + void SetServiceAPI(api_service *service_api); + int Load(); +protected: + ComponentManagerBase(); + int LateLoad(ifc_component *mod); + enum Phase + { + PHASE_INITIALIZE=0, /* components are still being added */ + PHASE_REGISTERED=1, /* RegisterServices() has been called on all components */ + PHASE_LOADING=2, /* OnLoading() has been called on all components */ + PHASE_LOADED=3, /* OnLoaded() has been called on all components */ + }; + Phase phase; + typedef nu::PtrDeque<ifc_component> ComponentList; + ComponentList components; + api_service *service_api; + ifc_component_sync *component_sync; +private: + /* your implementation needs to override this. You should call FreeLibrary(component->component_info.hModule); or dlclose(component->component_info.dl_handle); or similar */ + virtual void CloseComponent(ifc_component *component)=0; +}; diff --git a/Src/replicant/nswasabi/ID3v1Metadata.cpp b/Src/replicant/nswasabi/ID3v1Metadata.cpp new file mode 100644 index 00000000..2883401e --- /dev/null +++ b/Src/replicant/nswasabi/ID3v1Metadata.cpp @@ -0,0 +1,187 @@ +#include "ID3v1Metadata.h" +#include "metadata/MetadataKeys.h" +#include <stdlib.h> + +api_metadata *ID3v1Metadata::metadata_api=0; + +ID3v1Metadata::ID3v1Metadata() +{ + id3v1_tag=0; +} + +ID3v1Metadata::~ID3v1Metadata() +{ +} + +int ID3v1Metadata::Initialize(api_metadata *metadata_api) +{ + ID3v1Metadata::metadata_api = metadata_api; + return NErr_Success; +} + +int ID3v1Metadata::Initialize(nsid3v1_tag_t tag) +{ + id3v1_tag = tag; + this->metadata_api = metadata_api; + return NErr_Success; +} + +/* ifc_metadata implementation */ +int ID3v1Metadata::Metadata_GetField(int field, unsigned int index, nx_string_t *value) +{ + if (!id3v1_tag) + return NErr_Unknown; + + switch (field) + { + case MetadataKeys::TITLE: + return index?NErr_EndOfEnumeration:NSID3v1_Get_Title(id3v1_tag, value); + case MetadataKeys::ARTIST: + return index?NErr_EndOfEnumeration:NSID3v1_Get_Artist(id3v1_tag, value); + case MetadataKeys::ALBUM: + return index?NErr_EndOfEnumeration:NSID3v1_Get_Album(id3v1_tag, value); + case MetadataKeys::YEAR: + return index?NErr_EndOfEnumeration:NSID3v1_Get_Year(id3v1_tag, value); + case MetadataKeys::COMMENT: + return index?NErr_EndOfEnumeration:NSID3v1_Get_Comment(id3v1_tag, value); + case MetadataKeys::TRACK: + return index?NErr_EndOfEnumeration:NSID3v1_Get_Track(id3v1_tag, value); + case MetadataKeys::GENRE: + { + if (!metadata_api) + return NErr_Unknown; + if (index > 0) + return NErr_EndOfEnumeration; + + uint8_t genre_id; + int ret = NSID3v1_Int_Get_Genre(id3v1_tag, &genre_id); + if (ret != NErr_Success) + return ret; + + nx_string_t genre; + ret = metadata_api->GetGenre(genre_id, &genre); + if (ret == NErr_Success) + { + *value = NXStringRetain(genre); + return NErr_Success; + } + else if (ret == NErr_Unknown) + { + return NErr_Empty; + } + else + { + return ret; + } + } + } + + return NErr_Unknown; +} + +int ID3v1Metadata::Metadata_GetInteger(int field, unsigned int index, int64_t *value) +{ + if (!id3v1_tag) + return NErr_Unknown; + + switch (field) + { + case MetadataKeys::YEAR: + { + if (index > 0) + return NErr_EndOfEnumeration; + unsigned int year; + int ret = NSID3v1_Int_Get_Year(id3v1_tag, &year); + if (ret == NErr_Success) + *value = (int64_t)year; + return ret; + } + case MetadataKeys::TRACK: + { + if (index > 0) + return NErr_EndOfEnumeration; + uint8_t track; + int ret = NSID3v1_Int_Get_Track(id3v1_tag, &track); + if (ret == NErr_Success) + *value = (int64_t)track; + return ret; + } + } + return NErr_Unknown; +} + +int ID3v1Metadata::Metadata_GetReal(int field, unsigned int index, double *value) +{ + if (!id3v1_tag) + return NErr_Unknown; + + return NErr_Unknown; +} + +int ID3v1Metadata::MetadataEditor_SetField(int field, unsigned int index, nx_string_t value) +{ + if (!id3v1_tag) + return NErr_NullPointer; + + switch (field) + { + case MetadataKeys::TITLE: + return index?NErr_EndOfEnumeration:NSID3v1_Set_Title(id3v1_tag, value); + case MetadataKeys::ARTIST: + return index?NErr_EndOfEnumeration:NSID3v1_Set_Artist(id3v1_tag, value); + case MetadataKeys::ALBUM: + return index?NErr_EndOfEnumeration:NSID3v1_Set_Album(id3v1_tag, value); + case MetadataKeys::YEAR: + return index?NErr_EndOfEnumeration:NSID3v1_Set_Year(id3v1_tag, value); + case MetadataKeys::COMMENT: + return index?NErr_EndOfEnumeration:NSID3v1_Set_Comment(id3v1_tag, value); + case MetadataKeys::TRACK: + return index?NErr_EndOfEnumeration:NSID3v1_Set_Track(id3v1_tag, value); + + case MetadataKeys::GENRE: + { + if (!metadata_api) + return NErr_Unknown; + if (index > 0) + return NErr_EndOfEnumeration; + + uint8_t genre_id; + int ret = metadata_api->GetGenreID(value, &genre_id); + if (ret == NErr_Success) + return NSID3v1_Int_Set_Genre(id3v1_tag, genre_id); + else + return NSID3v1_Int_Set_Genre(id3v1_tag, 0xFF); + } + + + } + + return NErr_Unknown; +} + +int ID3v1Metadata::MetadataEditor_SetInteger(int field, unsigned int index, int64_t value) +{ + if (!id3v1_tag) + return NErr_NullPointer; + + if (index != 0) + return NErr_EndOfEnumeration; + + switch (field) + { + case MetadataKeys::YEAR: + return NSID3v1_Int_Set_Year(id3v1_tag, (unsigned int)value); + case MetadataKeys::TRACK: + if (value < 0 || value > 255) + return NErr_ParameterOutOfRange; + return NSID3v1_Int_Set_Track(id3v1_tag, (uint8_t)value); + case MetadataKeys::GENRE: + if (value < 0 || value > 255) + return NErr_ParameterOutOfRange; + return NSID3v1_Int_Set_Genre(id3v1_tag, (uint8_t)value); + } + + return NErr_Unknown; +} + +#undef DESCRIPTION diff --git a/Src/replicant/nswasabi/ID3v1Metadata.h b/Src/replicant/nswasabi/ID3v1Metadata.h new file mode 100644 index 00000000..c6da027c --- /dev/null +++ b/Src/replicant/nswasabi/ID3v1Metadata.h @@ -0,0 +1,26 @@ +#pragma once +#include "metadata/metadata.h" +#include "nsid3v1/nsid3v1.h" + +/* this class mimics ifc_metadata and ifc_metadata_editor, but doesn't inherit (because it's not given out directly) */ +class ID3v1Metadata +{ +public: + ID3v1Metadata(); + ~ID3v1Metadata(); + + static int Initialize(api_metadata *metadata_api); + int Initialize(nsid3v1_tag_t tag); + + /* ifc_metadata implementation */ + int WASABICALL Metadata_GetField(int field, unsigned int index, nx_string_t *value); + int WASABICALL Metadata_GetInteger(int field, unsigned int index, int64_t *value); + int WASABICALL Metadata_GetReal(int field, unsigned int index, double *value); + + /* ifc_metadata_editor implementation */ + int WASABICALL MetadataEditor_SetField(int field, unsigned int index, nx_string_t value); + int WASABICALL MetadataEditor_SetInteger(int field, unsigned int index, int64_t value); +private: + nsid3v1_tag_t id3v1_tag; + static api_metadata *metadata_api; +}; diff --git a/Src/replicant/nswasabi/ID3v2Metadata.cpp b/Src/replicant/nswasabi/ID3v2Metadata.cpp new file mode 100644 index 00000000..a1b49212 --- /dev/null +++ b/Src/replicant/nswasabi/ID3v2Metadata.cpp @@ -0,0 +1,1087 @@ +#include "ID3v2Metadata.h" +#include "metadata/MetadataKeys.h" +#include "nswasabi/ReferenceCounted.h" +#include <stdlib.h> +#include <stdio.h> + +api_metadata *ID3v2Metadata::metadata_api=0; + +static inline bool TestFlag(int flags, int flag_to_check) +{ + if (flags & flag_to_check) + return true; + return false; +} + +ID3v2Metadata::ID3v2Metadata() +{ + id3v2_tag=0; + +#ifdef __APPLE__ + number_formatter = NULL; +#endif +} + +ID3v2Metadata::~ID3v2Metadata() +{ +#ifdef __APPLE__ + if (NULL != number_formatter) + CFRelease(number_formatter); +#endif +} + +int ID3v2Metadata::Initialize(api_metadata *metadata_api) +{ + ID3v2Metadata::metadata_api = metadata_api; + return NErr_Success; +} + +int ID3v2Metadata::Initialize(nsid3v2_tag_t tag) +{ + id3v2_tag = tag; + + return NErr_Success; +} + +int ID3v2Metadata::GetGenre(int index, nx_string_t *value) +{ + nx_string_t genre=0; + int ret = NSID3v2_Tag_Text_Get(id3v2_tag, NSID3V2_FRAME_CONTENTTYPE, &genre, 0); + if (ret != NErr_Success) + return ret; + + if (index > 0) + return NErr_EndOfEnumeration; + + if (genre) + { + *value = genre; +#ifdef _WIN32 + // parse the (##) out of it + wchar_t *tmp = genre->string; + while (*tmp == ' ') tmp++; + if (!wcsncmp(tmp, L"(RX)", 4)) + { + *value = NXStringCreateFromUTF8("Remix"); + NXStringRelease(genre); + if (*value) + return NErr_Success; + else + return NErr_OutOfMemory; + } + else if (!wcsncmp(tmp, L"(CR)", 4)) + { + *value = NXStringCreateFromUTF8("Cover"); + NXStringRelease(genre); + if (*value) + return NErr_Success; + else + return NErr_OutOfMemory; + } + + if (*tmp == '(' || (*tmp >= '0' && *tmp <= '9')) // both (%d) and %d forms + { + int noparam = 0; + + if (*tmp == '(') tmp++; + else noparam = 1; + size_t genre_index = _wtoi(tmp); + int cnt = 0; + while (*tmp >= '0' && *tmp <= '9') cnt++, tmp++; + while (*tmp == ' ') tmp++; + + if (((!*tmp && noparam) || (!noparam && *tmp == ')')) && cnt > 0) + { + if (genre_index < 256 && metadata_api) + { + int ret = metadata_api->GetGenre(genre_index, value); + if (ret == NErr_Success) + { + NXStringRetain(*value); + NXStringRelease(genre); + return ret; + } + } + } + } +#elif defined(__APPLE__) + int ret = NErr_Success; + + CFMutableStringRef mutable_genre = CFStringCreateMutableCopy(NULL, 0, genre); + CFStringTrimWhitespace(mutable_genre); + + CFIndex mutable_genre_length = CFStringGetLength(mutable_genre); + + if (kCFCompareEqualTo == CFStringCompareWithOptionsAndLocale(mutable_genre, + CFSTR("(RX)"), + CFRangeMake(0, mutable_genre_length), + 0, + NULL)) + { + NXStringRelease(genre); + *value = CFSTR("Remix"); + ret = NErr_Success; + } + else if (kCFCompareEqualTo == CFStringCompareWithOptionsAndLocale(mutable_genre, + CFSTR("(CR)"), + CFRangeMake(0, mutable_genre_length), + 0, + NULL)) + { + NXStringRelease(genre); + *value = CFSTR("Cover"); + ret = NErr_Success; + } + else + { + CFStringTrim(mutable_genre, CFSTR("(")); + CFStringTrim(mutable_genre, CFSTR(")")); + mutable_genre_length = CFStringGetLength(mutable_genre); + if (mutable_genre_length > 0 + && mutable_genre_length < 4) + { + if (NULL == number_formatter) + { + CFLocaleRef locale = CFLocaleCreate(NULL, CFSTR("en_US_POSIX")); + number_formatter = CFNumberFormatterCreate(NULL, locale, kCFNumberFormatterDecimalStyle); + CFRelease(locale); + } + + SInt8 genre_index; + CFRange number_range = CFRangeMake(0, mutable_genre_length); + if (NULL != number_formatter + && false != CFNumberFormatterGetValueFromString(number_formatter, + mutable_genre, + &number_range, + kCFNumberSInt8Type, + &genre_index) + && number_range.length == mutable_genre_length + && number_range.location == 0) + { + + if (genre_index >= 0 + && genre_index < 256 + && metadata_api) + { + int ret = metadata_api->GetGenre(genre_index, value); + if (ret == NErr_Success) + { + NXStringRetain(*value); + NXStringRelease(genre); + } + ret = NErr_Success; + } + } + } + } + + CFRelease(mutable_genre); + return ret; +#elif defined(__linux__) + char *tmp = genre->string; + while (*tmp == ' ') tmp++; + + if (!strncmp(tmp, "(RX)", 4)) + { + NXStringRelease(genre); + return NXStringCreateWithUTF8(value, "Remix"); + } + else if (!strncmp(tmp, "(CR)", 4)) + { + NXStringRelease(genre); + return NXStringCreateWithUTF8(value, "Cover"); + } + + if (*tmp == '(' || (*tmp >= '0' && *tmp <= '9')) // both (%d) and %d forms + { + int noparam = 0; + + if (*tmp == '(') tmp++; + else noparam = 1; + size_t genre_index = atoi(tmp); + int cnt = 0; + while (*tmp >= '0' && *tmp <= '9') cnt++, tmp++; + while (*tmp == ' ') tmp++; + + if (((!*tmp && noparam) || (!noparam && *tmp == ')')) && cnt > 0) + { + if (genre_index < 256 && metadata_api) + { + int ret = metadata_api->GetGenre(genre_index, value); + if (ret == NErr_Success) + { + NXStringRetain(*value); + NXStringRelease(genre); + return ret; + } + } + } + } +#else +#error port me! +#endif + } + return NErr_Success; +} + +static int ID3v2_GetText(nsid3v2_tag_t id3v2_tag, int frame_enum, unsigned int index, nx_string_t *value) +{ + if (!id3v2_tag) + return NErr_Empty; + + nsid3v2_frame_t frame; + int ret = NSID3v2_Tag_GetFrame(id3v2_tag, frame_enum, &frame); + if (ret != NErr_Success) + return ret; + + if (index > 0) + return NErr_EndOfEnumeration; + + return NSID3v2_Frame_Text_Get(frame, value, 0); +} + +static int ID3v2_GetTXXX(nsid3v2_tag_t id3v2_tag, const char *description, unsigned int index, nx_string_t *value) +{ + if (!id3v2_tag) + return NErr_Empty; + + nsid3v2_frame_t frame; + int ret = NSID3v2_Tag_TXXX_Find(id3v2_tag, description, &frame, 0); + if (ret != NErr_Success) + return ret; + + if (index > 0) + return NErr_EndOfEnumeration; + + return NSID3v2_Frame_UserText_Get(frame, 0, value, 0); +} + +static int ID3v2_GetComments(nsid3v2_tag_t id3v2_tag, const char *description, unsigned int index, nx_string_t *value) +{ + if (!id3v2_tag) + return NErr_Empty; + + nsid3v2_frame_t frame; + int ret = NSID3v2_Tag_Comments_Find(id3v2_tag, description, &frame, 0); + if (ret != NErr_Success) + return ret; + + if (index > 0) + return NErr_EndOfEnumeration; + + return NSID3v2_Frame_Comments_Get(frame, 0, 0, value, 0); +} + +// only one of value1 or value2 should be non-NULL +static int SplitSlash(nx_string_t track, nx_string_t *value1, nx_string_t *value2) +{ + char track_utf8[64]; + size_t bytes_copied; + int ret; + ret = NXStringGetBytes(&bytes_copied, track, track_utf8, 64, nx_charset_utf8, nx_string_get_bytes_size_null_terminate); + if (ret == NErr_Success) + { + size_t len = strcspn(track_utf8, "/"); + + if (value2) + { + const char *second = &track_utf8[len]; + if (*second) + second++; + + if (!*second) + return NErr_Empty; + + return NXStringCreateWithUTF8(value2, second); + } + else + { + if (len == 0) + return NErr_Empty; + return NXStringCreateWithBytes(value1, track_utf8, len, nx_charset_utf8); + } + + return NErr_Success; + } + + return ret; +} + +/* ifc_metadata implementation */ +int ID3v2Metadata::Metadata_GetField(int field, unsigned int index, nx_string_t *value) +{ + if (!id3v2_tag) + return NErr_Unknown; + + int ret; + + switch (field) + { + case MetadataKeys::ARTIST: + return ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_LEADARTIST, index, value); + + case MetadataKeys::ALBUM_ARTIST: + ret = ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_BAND, index, value); /* Windows Media Player style */ + if (ret == NErr_Success || ret == NErr_EndOfEnumeration) + return ret; + + ret = ID3v2_GetTXXX(id3v2_tag, "ALBUM ARTIST", index, value); /* foobar 2000 style */ + if (ret == NErr_Success || ret == NErr_EndOfEnumeration) + return ret; + + ret = ID3v2_GetTXXX(id3v2_tag, "ALBUMARTIST", index, value); /* mp3tag style */ + if (ret == NErr_Success || ret == NErr_EndOfEnumeration) + return ret; + + return ID3v2_GetTXXX(id3v2_tag, "Band", index, value); /* audacity style */ + + case MetadataKeys::ALBUM: + return ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_ALBUM, index, value); + + case MetadataKeys::TITLE: + return ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_TITLE, index, value); + + case MetadataKeys::GENRE: + return GetGenre(index, value); + + case MetadataKeys::TRACK: + { + ReferenceCountedNXString track; + ret = ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_TRACK, index, &track); + if (ret == NErr_Success) + return SplitSlash(track, value, 0); + + return ret; + } + break; + + case MetadataKeys::TRACKS: +{ + ReferenceCountedNXString track; + ret = ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_TRACK, index, &track); + if (ret == NErr_Success) + return SplitSlash(track, 0, value); + + return ret; + } + break; + + case MetadataKeys::YEAR: + ret = ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_RECORDINGTIME, index, value); + if (ret == NErr_Success || ret == NErr_EndOfEnumeration) + return ret; + + return ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_YEAR, index, value); + + case MetadataKeys::DISC: + { + ReferenceCountedNXString track; + ret = ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_PARTOFSET, index, &track); + if (ret == NErr_Success) + return SplitSlash(track, value, 0); + + return ret; + } + break; + + case MetadataKeys::DISCS: + { + ReferenceCountedNXString track; + ret = ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_PARTOFSET, index, &track); + if (ret == NErr_Success) + return SplitSlash(track, 0, value); + + return ret; + } + break; + + case MetadataKeys::COMPOSER: + return ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_COMPOSER, index, value); + + case MetadataKeys::PUBLISHER: + return ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_PUBLISHER, index, value); + + case MetadataKeys::BPM: + return ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_BPM, index, value); + + case MetadataKeys::COMMENT: + return ID3v2_GetComments(id3v2_tag, "", index, value); + // TODO case MetadataKeys::PLAY_COUNT: + // TODO case MetadataKeys::RATING: + + case MetadataKeys::TRACK_GAIN: + return ID3v2_GetTXXX(id3v2_tag, "replaygain_track_gain", index, value); + + case MetadataKeys::TRACK_PEAK: + return ID3v2_GetTXXX(id3v2_tag, "replaygain_track_peak", index, value); + + case MetadataKeys::ALBUM_GAIN: + return ID3v2_GetTXXX(id3v2_tag, "replaygain_album_gain", index, value); + + case MetadataKeys::ALBUM_PEAK: + return ID3v2_GetTXXX(id3v2_tag, "replaygain_album_peak", index, value); + } + + return NErr_Unknown; +} + +static int IncSafe(const char *&value, size_t &value_length, size_t increment_length) +{ + /* eat leading spaces */ + while (*value == ' ' && value_length) + { + value++; + value_length--; + } + + if (increment_length > value_length) + return NErr_NeedMoreData; + + value += increment_length; + value_length -= increment_length; + /* eat trailing spaces */ + while (*value == ' ' && value_length) + { + value++; + value_length--; + } + + return NErr_Success; +} + +static int SplitSlashInteger(nx_string_t track, unsigned int *value1, unsigned int *value2) +{ + char track_utf8[64]; + size_t bytes_copied; + int ret; + ret = NXStringGetBytes(&bytes_copied, track, track_utf8, 64, nx_charset_utf8, nx_string_get_bytes_size_null_terminate); + if (ret == NErr_Success) + { + size_t len = strcspn(track_utf8, "/"); + + if (track_utf8[len]) + *value2 = strtoul(&track_utf8[len+1], 0, 10); + else + *value2 = 0; + + track_utf8[len]=0; + *value1 = strtoul(track_utf8, 0, 10); + + return NErr_Success; + } + + return ret; +} + +int ID3v2Metadata::Metadata_GetInteger(int field, unsigned int index, int64_t *value) +{ + if (!id3v2_tag) + return NErr_Unknown; + + switch(field) + { + case MetadataKeys::TRACK: + { + ReferenceCountedNXString track; + int ret; + ret = ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_TRACK, index, &track); + if (ret == NErr_Success) + { + unsigned int itrack, itracks; + ret = SplitSlashInteger(track, &itrack, &itracks); + if (ret == NErr_Success) + { + if (itrack == 0) + return NErr_Empty; + + *value = itrack; + return NErr_Success; + + } + } + return ret; + } + break; + + case MetadataKeys::TRACKS: + { + ReferenceCountedNXString track; + int ret; + ret = ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_TRACK, index, &track); + if (ret == NErr_Success) + { + unsigned int itrack, itracks; + ret = SplitSlashInteger(track, &itrack, &itracks); + if (ret == NErr_Success) + { + if (itracks == 0) + return NErr_Empty; + + *value = itracks; + return NErr_Success; + } + } + return ret; + } + break; + + case MetadataKeys::DISC: + { + ReferenceCountedNXString track; + int ret; + ret = ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_PARTOFSET, index, &track); + if (ret == NErr_Success) + { + unsigned int idisc, idiscs; + ret = SplitSlashInteger(track, &idisc, &idiscs); + if (ret == NErr_Success) + { + if (idisc == 0) + return NErr_Empty; + + *value = idisc; + return NErr_Success; + + } + } + return ret; + } + break; + + case MetadataKeys::DISCS: + { + ReferenceCountedNXString track; + int ret; + ret = ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_PARTOFSET, index, &track); + if (ret == NErr_Success) + { + unsigned int idisc, idiscs; + ret = SplitSlashInteger(track, &idisc, &idiscs); + if (ret == NErr_Success) + { + if (idiscs == 0) + return NErr_Empty; + + *value = idiscs; + return NErr_Success; + + } + } + return ret; + } + break; + + case MetadataKeys::BPM: + { + ReferenceCountedNXString bpm; + int ret; + ret = ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_BPM, index, &bpm); + if (ret == NErr_Success) + { + /* TODO: benski> implement NXStringGetInt64Value */ + int value32; + ret = NXStringGetIntegerValue(bpm, &value32); + if (ret != NErr_Success) + return ret; + *value = value32; + return NErr_Success; + } + return ret; + } + case MetadataKeys::PREGAP: + { + ReferenceCountedNXString str; + char language[3]; + int ret = NSID3v2_Tag_Comments_Get(id3v2_tag, "iTunSMPB", language, &str, 0); + if (ret == NErr_Success) + { + if (index > 0) + return NErr_EndOfEnumeration; + + const char *itunsmpb; + size_t itunsmpb_length; + char temp[64] = {0}; + if (NXStringGetCString(str, temp, sizeof(temp)/sizeof(*temp), &itunsmpb, &itunsmpb_length) == NErr_Success) + { + /* skip first set of meaningless values */ + if (IncSafe(itunsmpb, itunsmpb_length, 8) == NErr_Success && itunsmpb_length >= 8) + { + /* read pre-gap */ + *value = strtoul(itunsmpb, 0, 16); + return NErr_Success; + } + } + return NErr_Error; + } + else + return ret; + + } + case MetadataKeys::POSTGAP: + { + ReferenceCountedNXString str; + char language[3]; + int ret = NSID3v2_Tag_Comments_Get(id3v2_tag, "iTunSMPB", language, &str, 0); + if (ret == NErr_Success) + { + if (index > 0) + return NErr_EndOfEnumeration; + + const char *itunsmpb; + size_t itunsmpb_length; + char temp[64] = {0}; + if (NXStringGetCString(str, temp, sizeof(temp)/sizeof(*temp), &itunsmpb, &itunsmpb_length) == NErr_Success) + { + /* two separate calls so we can skip spaces properly */ + if (IncSafe(itunsmpb, itunsmpb_length, 8) == NErr_Success && itunsmpb_length >= 8 + && IncSafe(itunsmpb, itunsmpb_length, 8) == NErr_Success && itunsmpb_length >= 8) + { + *value = strtoul(itunsmpb, 0, 16); + return NErr_Success; + } + + } + return NErr_Error; + } + else + return ret; + + } + } + return NErr_Unknown; +} + +int ID3v2Metadata::Metadata_GetReal(int field, unsigned int index, double *value) +{ + if (!id3v2_tag) + return NErr_Unknown; + + int ret; + nx_string_t str; + switch (field) + { + case MetadataKeys::TRACK_GAIN: + ret = ID3v2_GetTXXX(id3v2_tag, "replaygain_track_gain", index, &str); + if (ret == NErr_Success) + { + ret = NXStringGetDoubleValue(str, value); + NXStringRelease(str); + } + return ret; + case MetadataKeys::TRACK_PEAK: + ret = ID3v2_GetTXXX(id3v2_tag, "replaygain_track_peak", index, &str); + if (ret == NErr_Success) + { + ret = NXStringGetDoubleValue(str, value); + NXStringRelease(str); + } + return ret; + case MetadataKeys::ALBUM_GAIN: + ret = ID3v2_GetTXXX(id3v2_tag, "replaygain_album_gain", index, &str); + if (ret == NErr_Success) + { + ret = NXStringGetDoubleValue(str, value); + NXStringRelease(str); + } + return ret; + case MetadataKeys::ALBUM_PEAK: + ret = ID3v2_GetTXXX(id3v2_tag, "replaygain_album_peak", index, &str); + if (ret == NErr_Success) + { + ret = NXStringGetDoubleValue(str, value); + NXStringRelease(str); + } + return ret; + } + return NErr_Unknown; +} + +static int ArtLookupType(uint8_t *id3v2_type, int metadata_key) +{ + switch(metadata_key) + { + case MetadataKeys::ALBUM: + *id3v2_type = 3; + return NErr_Success; + } + return NErr_Unknown; +} + +static int NXStringCreateWithMIME(nx_string_t *mime_type, nx_string_t in) +{ + if (!mime_type) + return NErr_Success; + + char temp[128]; + size_t copied; + int ret = NXStringGetBytes(&copied, in, temp, 128, nx_charset_ascii, nx_string_get_bytes_size_null_terminate); + if (ret != NErr_Success) + return ret; + + if (strstr(temp, "/") != 0) + { + *mime_type = NXStringRetain(in); + return NErr_Success; + } + else + { + char temp2[128]; +#ifdef _WIN32 + _snprintf(temp2, 127, "image/%s", temp); +#else + snprintf(temp2, 127, "image/%s", temp); +#endif + temp2[127]=0; + + return NXStringCreateWithUTF8(mime_type, temp2); + } +} + +int ID3v2Metadata::Metadata_GetArtwork(int field, unsigned int index, artwork_t *artwork, data_flags_t flags) +{ + if (!id3v2_tag) + return NErr_Unknown; + + uint8_t id3v2_picture_type; + int ret = ArtLookupType(&id3v2_picture_type, field); + if (ret != NErr_Success) + return ret; + + if (!id3v2_tag) + return NErr_Empty; + + bool found_one=false; + nsid3v2_frame_t frame=0; + ret = NSID3v2_Tag_GetFrame(id3v2_tag, NSID3V2_FRAME_PICTURE, &frame); + if (ret != NErr_Success) + return ret; + + for (;;) + { + uint8_t this_type; + if (NSID3v2_Frame_Picture_Get(frame, 0, &this_type, 0, 0, 0, 0) == NErr_Success && (this_type == id3v2_picture_type || (id3v2_picture_type == 3 && this_type == 0))) + { + found_one=true; + if (index == 0) + { + + if (artwork) + { + nx_data_t data=0; + + if (flags != DATA_FLAG_NONE) + { + const void *picture_data; + size_t picture_length; + ReferenceCountedNXString mime_local, description; + + ret = NSID3v2_Frame_Picture_Get(frame, TestFlag(flags, DATA_FLAG_MIME)?(&mime_local):0, &this_type, TestFlag(flags, DATA_FLAG_DESCRIPTION)?(&description):0, &picture_data, &picture_length, 0); + if (ret != NErr_Success) + return ret; + + if (TestFlag(flags, DATA_FLAG_DATA)) + { + ret = NXDataCreate(&data, picture_data, picture_length); + if (ret != NErr_Success) + return ret; + } + else + { + ret = NXDataCreateEmpty(&data); + if (ret != NErr_Success) + return ret; + } + + if (mime_local) + { + ReferenceCountedNXString mime_type; + ret = NXStringCreateWithMIME(&mime_type, mime_local); + if (ret != NErr_Success) + { + NXDataRelease(data); + return ret; + } + NXDataSetMIME(data, mime_type); + } + + if (description) + { + NXDataSetDescription(data, description); + } + } + artwork->data = data; + /* id3v2 doesn't store height and width, so zero these */ + artwork->width=0; + artwork->height=0; + } + return NErr_Success; + } + else + { + index--; // keep looking + } + } + + if (NSID3v2_Tag_GetNextFrame(id3v2_tag, frame, &frame) != NErr_Success) + { + if (found_one) + return NErr_EndOfEnumeration; + else + return NErr_Empty; + } + } +} + +static int SetText(nsid3v2_tag_t id3v2_tag, int frame_id, unsigned int index, nx_string_t value) +{ + if (index > 0) + return NErr_Success; + + if (!value) + { + nsid3v2_frame_t frame; + if (NSID3v2_Tag_GetFrame(id3v2_tag, frame_id, &frame) == NErr_Success) + { + for(;;) + { + nsid3v2_frame_t next; + int ret = NSID3v2_Tag_GetNextFrame(id3v2_tag, frame, &next); + NSID3v2_Tag_RemoveFrame(id3v2_tag, frame); + if (ret != NErr_Success) + break; + frame=next; + } + } + return NErr_Success; + } + else + { + return NSID3v2_Tag_Text_Set(id3v2_tag, frame_id, value, 0); + } +} + +static int SetTXXX(nsid3v2_tag_t id3v2_tag, const char *description, unsigned int index, nx_string_t value, int text_flags) +{ + if (index > 0) + return NErr_EndOfEnumeration; + + if (!value) + { + nsid3v2_frame_t frame; + for(;;) + { + if (NSID3v2_Tag_TXXX_Find(id3v2_tag, description, &frame, text_flags) == NErr_Success) + NSID3v2_Tag_RemoveFrame(id3v2_tag, frame); + else + return NErr_Success; + } + } + else + { + return NSID3v2_Tag_TXXX_Set(id3v2_tag, description, value, 0); + } +} + +static int SetComments(nsid3v2_tag_t id3v2_tag, const char *description, unsigned int index, nx_string_t value, int text_flags) +{ + if (index > 0) + return NErr_EndOfEnumeration; + + if (!value) + { + nsid3v2_frame_t frame; + for(;;) + { + if (NSID3v2_Tag_Comments_Find(id3v2_tag, description, &frame, text_flags) == NErr_Success) + NSID3v2_Tag_RemoveFrame(id3v2_tag, frame); + else + return NErr_Success; + } + } + else + { + return NSID3v2_Tag_Comments_Set(id3v2_tag, description, "\0\0\0", value, 0); + } +} + +int ID3v2Metadata::MetadataEditor_SetField(int field, unsigned int index, nx_string_t value) +{ + int ret; + + switch (field) + { + case MetadataKeys::ARTIST: + return SetText(id3v2_tag, NSID3V2_FRAME_LEADARTIST, index, value); + + case MetadataKeys::ALBUM_ARTIST: + ret = SetText(id3v2_tag, NSID3V2_FRAME_BAND, index, value); + /* delete some of the alternates */ + SetTXXX(id3v2_tag, "ALBUM ARTIST", index, 0, 0); /* foobar 2000 style */ + SetTXXX(id3v2_tag, "ALBUMARTIST", index, 0, 0); /* mp3tag style */ + + if (!value) /* this might be a valid field, so only delete it if we're specifically deleting album artist (because otherwise, if it's here it's going to get picked up by GetField */ + SetTXXX(id3v2_tag, "Band", index, 0, 0); /* audacity style */ + + return ret; + + case MetadataKeys::ALBUM: + return SetText(id3v2_tag, NSID3V2_FRAME_ALBUM, index, value); + + case MetadataKeys::TITLE: + return SetText(id3v2_tag, NSID3V2_FRAME_TITLE, index, value); + + case MetadataKeys::GENRE: + return SetText(id3v2_tag, NSID3V2_FRAME_CONTENTTYPE, index, value); + + case MetadataKeys::YEAR: + /* try to set "newer" style TDRC, first */ + ret = SetText(id3v2_tag, NSID3V2_FRAME_RECORDINGTIME, index, value); + if (ret == NErr_Success) + { + /* if it succeeded, remove the older TYER tag */ + SetText(id3v2_tag, NSID3V2_FRAME_RECORDINGTIME, index, 0); + return ret; + } + + /* fall back to using TYER */ + return SetText(id3v2_tag, NSID3V2_FRAME_RECORDINGTIME, index, 0); + + case MetadataKeys::TRACK: + return SetText(id3v2_tag, NSID3V2_FRAME_TRACK, index, value); + + case MetadataKeys::DISC: + return SetText(id3v2_tag, NSID3V2_FRAME_PARTOFSET, index, value); + + case MetadataKeys::COMPOSER: + return SetText(id3v2_tag, NSID3V2_FRAME_COMPOSER, index, value); + + case MetadataKeys::PUBLISHER: + return SetText(id3v2_tag, NSID3V2_FRAME_PUBLISHER, index, value); + + case MetadataKeys::BPM: + return SetText(id3v2_tag, NSID3V2_FRAME_BPM, index, value); + + case MetadataKeys::COMMENT: + return SetComments(id3v2_tag, "", index, value, 0); + + case MetadataKeys::TRACK_GAIN: + return SetTXXX(id3v2_tag, "replaygain_track_gain", index, value, 0); + + case MetadataKeys::TRACK_PEAK: + return SetTXXX(id3v2_tag, "replaygain_track_peak", index, value, 0); + + case MetadataKeys::ALBUM_GAIN: + return SetTXXX(id3v2_tag, "replaygain_album_gain", index, value, 0); + + case MetadataKeys::ALBUM_PEAK: + return SetTXXX(id3v2_tag, "replaygain_album_peak", index, value, 0); + } + + return NErr_Unknown; +} + +static int ID3v2_SetPicture(nsid3v2_frame_t frame, uint8_t id3v2_picture_type, artwork_t *artwork, data_flags_t flags) +{ + int ret; + + const void *picture_data; + size_t picture_length; + ret = NXDataGet(artwork->data, &picture_data, &picture_length); + if (ret != NErr_Success) + return ret; + ReferenceCountedNXString mime_type, description; + if (TestFlag(flags, DATA_FLAG_MIME)) + NXDataGetMIME(artwork->data, &mime_type); + if (TestFlag(flags, DATA_FLAG_DESCRIPTION)) + NXDataGetDescription(artwork->data, &description); + return NSID3v2_Frame_Picture_Set(frame, mime_type, id3v2_picture_type, description, picture_data, picture_length, 0); +} + +int ID3v2Metadata::MetadataEditor_SetArtwork(int field, unsigned int index, artwork_t *artwork, data_flags_t flags) +{ + uint8_t id3v2_picture_type; + int ret = ArtLookupType(&id3v2_picture_type, field); + if (ret != NErr_Success) + return ret; + + + nsid3v2_frame_t frame=0; + ret = NSID3v2_Tag_GetFrame(id3v2_tag, NSID3V2_FRAME_PICTURE, &frame); + if (ret != NErr_Success) + { + if (artwork && artwork->data) + { + /* create a new one and store */ + int ret = NSID3v2_Tag_CreateFrame(id3v2_tag, NSID3V2_FRAME_PICTURE, 0, &frame); + if (ret == NErr_Success) + { + ret = ID3v2_SetPicture(frame, id3v2_picture_type, artwork, flags); + if (ret == NErr_Success) + { + ret = NSID3v2_Tag_AddFrame(id3v2_tag, frame); + if (ret != NErr_Success) + { + NSID3v2_Tag_RemoveFrame(id3v2_tag, frame); + } + } + } + return ret; + } + else + return NErr_Success; + } + + for (;;) + { + /* iterate now, because we might delete the current frame */ + nsid3v2_frame_t next_frame=0; + if (NSID3v2_Tag_GetNextFrame(id3v2_tag, frame, &next_frame) != NErr_Success) + next_frame=0; /* just in case */ + + uint8_t this_type; + if (NSID3v2_Frame_Picture_Get(frame, 0, &this_type, 0, 0, 0, 0) == NErr_Success && (this_type == id3v2_picture_type || (id3v2_picture_type == 3 && this_type == 0))) + { + if (index == 0) + { + if (artwork && artwork->data) + { + return ID3v2_SetPicture(frame, id3v2_picture_type, artwork, flags); + } + else + { + NSID3v2_Tag_RemoveFrame(id3v2_tag, frame); + } + } + else + { + index--; // keep looking + } + } + + if (!next_frame) + { + if (!artwork || !artwork->data) + return NErr_Success; + else + { + /* create a new one and store */ + int ret = NSID3v2_Tag_CreateFrame(id3v2_tag, NSID3V2_FRAME_PICTURE, 0, &frame); + if (ret != NErr_Success) + return ret; + + ret = ID3v2_SetPicture(frame, id3v2_picture_type, artwork, flags); + if (ret != NErr_Success) + return ret; + ret = NSID3v2_Tag_AddFrame(id3v2_tag, frame); + if (ret != NErr_Success) + { + NSID3v2_Tag_RemoveFrame(id3v2_tag, frame); + } + return ret; + } + } + frame = next_frame; + } + + return NErr_NotImplemented; +} diff --git a/Src/replicant/nswasabi/ID3v2Metadata.h b/Src/replicant/nswasabi/ID3v2Metadata.h new file mode 100644 index 00000000..9fdf23b3 --- /dev/null +++ b/Src/replicant/nswasabi/ID3v2Metadata.h @@ -0,0 +1,34 @@ +#pragma once +#include "metadata/metadata.h" +#include "nsid3v2/nsid3v2.h" + +/* this class mimics ifc_metadata and ifc_metadata_editor, but doesn't inherit (because it's not given out directly) */ +class ID3v2Metadata +{ +public: + ID3v2Metadata(); + ~ID3v2Metadata(); + + static int Initialize(api_metadata *metadata_api); + int Initialize(nsid3v2_tag_t tag); + + /* ifc_metadata implementation */ + int WASABICALL Metadata_GetField(int field, unsigned int index, nx_string_t *value); + int WASABICALL Metadata_GetInteger(int field, unsigned int index, int64_t *value); + int WASABICALL Metadata_GetReal(int field, unsigned int index, double *value); + int WASABICALL Metadata_GetArtwork(int field, unsigned int index, artwork_t *artwork, data_flags_t flags); + + /* ifc_metadata_editor implementation */ + int WASABICALL MetadataEditor_SetField(int field, unsigned int index, nx_string_t value); + int WASABICALL MetadataEditor_SetArtwork(int field, unsigned int index, artwork_t *data, data_flags_t flags); +private: + nsid3v2_tag_t id3v2_tag; + + int GetGenre(int index, nx_string_t *value); + + static api_metadata *metadata_api; + +#ifdef __APPLE__ + CFNumberFormatterRef number_formatter; +#endif +};
\ No newline at end of file diff --git a/Src/replicant/nswasabi/Makefile b/Src/replicant/nswasabi/Makefile new file mode 100644 index 00000000..2a44c708 --- /dev/null +++ b/Src/replicant/nswasabi/Makefile @@ -0,0 +1,48 @@ +MODULE_NAME := nswasabi + +CPPSOURCES := ComponentManagerBase.cpp ApplicationBase.cpp ID3v2Metadata.cpp ID3v1Metadata.cpp PlaybackBase.cpp APEv2Metadata.cpp +#CSOURCES := utf.c ByteReader.c ByteWriter.c + +LIBRARY_FILENAME := lib$(MODULE_NAME).a +OUTPUT_PATH := ../build/$(MODULE_NAME) +ARCHIVE_PATH := ../build/lib +LIBRARY_FILEPATH := ../build/lib/$(LIBRARY_FILENAME) + +CPPOBJS := $(patsubst %.cpp,$(OUTPUT_PATH)/%.o,$(CPPSOURCES)) +CPPDEPS := $(patsubst %.o,$(OUTPUT_PATH)/%.d,$(CPPOBJS)) +COBJS := $(patsubst %.c,$(OUTPUT_PATH)/%.o,$(CSOURCES)) +CDEPS := $(patsubst %.o,$(OUTPUT_PATH)/%.d,$(COBJS)) + +OBJS := $(CPPOBJS) $(COBJS) +DEPS := $(CPPDEPS) $(CDEPS) + +CFLAGS=-I.. -fPIC #-fvisibility=hidden +CPPFLAGS := ${CFLAGS} + + + +build: build-dir $(LIBRARY_FILEPATH) + +build-dir: + @mkdir -p $(OUTPUT_PATH)/linux > /dev/null 2> /dev/null + @mkdir -p $(OUTPUT_PATH) > /dev/null 2> /dev/null + @mkdir -p $(ARCHIVE_PATH) > /dev/null 2> /dev/null + +dep: + @rm ${DEPS} + +$(OUTPUT_PATH)/%.o: %.cpp + #@echo Compiling $*.cpp + @$(CXX) $(CPPFLAGS) -MMD -MF $(OUTPUT_PATH)/$*.d -MT $(OUTPUT_PATH)/$*.o -c $*.cpp -o $(OUTPUT_PATH)/$*.o + +$(OUTPUT_PATH)/%.o: %.c + #@echo Compiling $*.c + @$(CC) $(CFLAGS) -MMD -MF $(OUTPUT_PATH)/$*.d -MT $(OUTPUT_PATH)/$*.o -c $*.c -o $(OUTPUT_PATH)/$*.o + +$(LIBRARY_FILEPATH): ${OBJS} + @$(AR) rcs $@ ${OBJS} + +clean: + -rm -f ${OBJS} $(LIBRARY_FILENAME) ${DEPS} + +-include $(DEPS) diff --git a/Src/replicant/nswasabi/MetadataChain.h b/Src/replicant/nswasabi/MetadataChain.h new file mode 100644 index 00000000..cc6de698 --- /dev/null +++ b/Src/replicant/nswasabi/MetadataChain.h @@ -0,0 +1,97 @@ +#pragma once +#include "metadata/ifc_metadata.h" + + +template <class metadata_t> +class MetadataChain : public metadata_t +{ +public: + MetadataChain() + { + parent_metadata=0; + } + + ~MetadataChain() + { + if (parent_metadata) + parent_metadata->Release(); + } + + static bool Unhandled(ns_error_t ret) + { + return (ret == NErr_NotImplemented || ret == NErr_Empty || ret == NErr_Unknown); + } + + ns_error_t SetParentMetadata(ifc_metadata *new_parent_metadata) + { + if (parent_metadata) + parent_metadata->Release(); + parent_metadata=new_parent_metadata; + if (parent_metadata) + parent_metadata->Retain(); + return NErr_Success; + } +protected: + ifc_metadata *parent_metadata; +private: + ns_error_t WASABICALL Metadata_GetField(int field, unsigned int index, nx_string_t *value) + { + ns_error_t ret = metadata_t::Metadata_GetField(field, index, value); + if (Unhandled(ret) && parent_metadata) + return parent_metadata->GetField(field, index, value); + else + return ret; + + } + + ns_error_t WASABICALL Metadata_GetInteger(int field, unsigned int index, int64_t *value) + { + ns_error_t ret = metadata_t::Metadata_GetInteger(field, index, value); + if (Unhandled(ret) && parent_metadata) + return parent_metadata->GetInteger(field, index, value); + else + return ret; + + } + + ns_error_t WASABICALL Metadata_GetReal(int field, unsigned int index, double *value) + { + ns_error_t ret = metadata_t::Metadata_GetReal(field, index, value); + if (Unhandled(ret) && parent_metadata) + return parent_metadata->GetReal(field, index, value); + else + return ret; + + } + + ns_error_t WASABICALL Metadata_GetArtwork(int field, unsigned int index, artwork_t *artwork, data_flags_t flags) + { + ns_error_t ret = metadata_t::Metadata_GetArtwork(field, index, artwork, flags); + if (Unhandled(ret) && parent_metadata) + return parent_metadata->GetArtwork(field, index, artwork, flags); + else + return ret; + + } + + ns_error_t WASABICALL Metadata_GetBinary(int field, unsigned int index, nx_data_t *data) + { + ns_error_t ret = metadata_t::Metadata_GetBinary(field, index, data); + if (Unhandled(ret) && parent_metadata) + return parent_metadata->GetBinary(field, index, data); + else + return ret; + + } + + ns_error_t WASABICALL Metadata_GetMetadata(int field, unsigned int index, ifc_metadata **metadata) + { + ns_error_t ret = metadata_t::Metadata_GetMetadata(field, index, metadata); + if (Unhandled(ret) && parent_metadata) + return parent_metadata->GetMetadata(field, index, metadata); + else + return ret; + + } + // TODO: ns_error_t WASABICALL Metadata_Serialize(nx_data_t *data) { return NErr_NotImplemented; } +}; diff --git a/Src/replicant/nswasabi/MetadataEditorChain.h b/Src/replicant/nswasabi/MetadataEditorChain.h new file mode 100644 index 00000000..7142acca --- /dev/null +++ b/Src/replicant/nswasabi/MetadataEditorChain.h @@ -0,0 +1,100 @@ +#pragma once +#include "metadata/ifc_metadata_editor.h" + + +template <class metadata_t> +class MetadataEditorChain : public metadata_t +{ +public: + MetadataEditorChain() + { + parent_metadata=0; + } + + ~MetadataEditorChain() + { + if (parent_metadata) + parent_metadata->Release(); + } + + static bool Unhandled(ns_error_t ret) + { + return (ret == NErr_NotImplemented || ret == NErr_Empty || ret == NErr_Unknown); + } + + ns_error_t SetParentMetadata(ifc_metadata_editor *new_parent_metadata) + { + if (parent_metadata) + parent_metadata->Release(); + parent_metadata=new_parent_metadata; + if (parent_metadata) + parent_metadata->Retain(); + return NErr_Success; + } +protected: + ifc_metadata_editor *parent_metadata; +private: + int WASABICALL MetadataEditor_Save() + { + if (parent_metadata) + return parent_metadata->Save(); + else + return NErr_NotImplemented; + } + + int WASABICALL MetadataEditor_SetField(int field, unsigned int index, nx_string_t value) + { + bool known=false; + if (parent_metadata && parent_metadata->SetField(field, index, value) == NErr_Success) + known=true; + if (metadata_t::MetadataEditor_SetField(field, index, value) == NErr_Success) + known=true; + + if (known) + return NErr_Success; + else + return NErr_Unknown; + } + + int WASABICALL MetadataEditor_SetInteger(int field, unsigned int index, int64_t value) + { + bool known=false; + if (parent_metadata && parent_metadata->SetInteger(field, index, value) == NErr_Success) + known=true; + if (metadata_t::MetadataEditor_SetInteger(field, index, value) == NErr_Success) + known=true; + + if (known) + return NErr_Success; + else + return NErr_Unknown; + } + + int WASABICALL MetadataEditor_SetReal(int field, unsigned int index, double value) + { + bool known=false; + if (parent_metadata && parent_metadata->SetReal(field, index, value) == NErr_Success) + known=true; + if (metadata_t::MetadataEditor_SetReal(field, index, value) == NErr_Success) + known=true; + + if (known) + return NErr_Success; + else + return NErr_Unknown; + } + + int WASABICALL MetadataEditor_SetArtwork(int field, unsigned int index, artwork_t *data, data_flags_t flags) + { + bool known=false; + if (parent_metadata && parent_metadata->SetArtwork(field, index, data, flags) == NErr_Success) + known=true; + if (metadata_t::MetadataEditor_SetArtwork(field, index, data, flags) == NErr_Success) + known=true; + + if (known) + return NErr_Success; + else + return NErr_Unknown; + } +};
\ No newline at end of file diff --git a/Src/replicant/nswasabi/ObjectFactory.h b/Src/replicant/nswasabi/ObjectFactory.h new file mode 100644 index 00000000..55a99820 --- /dev/null +++ b/Src/replicant/nswasabi/ObjectFactory.h @@ -0,0 +1,57 @@ +#pragma once + +#include "service/ifc_servicefactory.h" +#include "ReferenceCounted.h" +/* +====== Usage ====== +disp_t: your Dispatchable base class +implt_t: your implementation class + +ObjectFactory<disp_t, impl_t> myFactory; +impl_t myImplementation; + +//.... + +//during service registration +myFactory.Register(WASABI2_API_SVC); + +//during service deregistration +myFactory.Deregister(WASABI2_API_SVC); + +==== Class requirements ==== +your base or implementation class requires the following three static methods +static FOURCC getServiceType(); // return your type (e.g. WaSvc::OBJECT)... might already be defined in the dispatchable base class +static const char *getServiceName(); // return your service name +static GUID getServiceGuid(); // return your service GUID +*/ + +template <class impl_t, class disp_t> +class CountableObjectFactory : public ifc_serviceFactory +{ +public: + CountableObjectFactory() + { + } + + ~CountableObjectFactory() + { + } + + void Register(api_service *serviceManager) + { + serviceManager->Register(this); + } + + void Deregister(api_service *serviceManager) + { + serviceManager->Unregister(this); + } + +private: + GUID WASABICALL ServiceFactory_GetServiceType() { return impl_t::GetServiceType(); } + nx_string_t WASABICALL ServiceFactory_GetServiceName() { return impl_t::GetServiceName(); } + GUID WASABICALL ServiceFactory_GetGUID() { return impl_t::GetServiceGUID(); } // GUID per service factory, can be INVALID_GUID + void *WASABICALL ServiceFactory_GetInterface() { return static_cast<disp_t *>(new ReferenceCounted<impl_t>); } + int WASABICALL ServiceFactory_ServiceNotify(int msg, intptr_t param1 = 0, intptr_t param2 = 0) { return 0; } +}; + diff --git a/Src/replicant/nswasabi/PlaybackBase.cpp b/Src/replicant/nswasabi/PlaybackBase.cpp new file mode 100644 index 00000000..d3a94a6e --- /dev/null +++ b/Src/replicant/nswasabi/PlaybackBase.cpp @@ -0,0 +1,409 @@ +#include "PlaybackBase.h" +#include <stdlib.h> +#include <assert.h> +#ifdef __ANDROID__ +#include <android/log.h> // TODO: replace with generic logging API + +#else +#define ANDROID_LOG_INFO 0 +#define ANDROID_LOG_ERROR 1 +static void __android_log_print(int, const char *, const char *, ...) +{ +} +#endif +PlaybackBase::PlaybackBase() +{ + wake_flags=0; + last_wake_flags=0; + playback_thread=0; + secondary_parameters=0; + filename=0; + player=0; + queued_seek=0; + output_service=0; +} + +int PlaybackBase::Initialize(nx_uri_t filename, ifc_player *player) +{ + this->player = player; + this->filename = NXURIRetain(filename); + return NErr_Success; +} + +PlaybackBase::~PlaybackBase() +{ + if (secondary_parameters) + secondary_parameters->Release(); + if (filename) + NXURIRelease(filename); + if (queued_seek) + free(queued_seek); + if (playback_thread) + NXThreadJoin(playback_thread, 0); +} + +int PlaybackBase::Playback_Play(svc_output *output, ifc_playback_parameters *secondary_parameters) +{ + if (!playback_thread) + return 1; + + __android_log_print(ANDROID_LOG_INFO, "libreplicant", "[PlaybackBase] Play"); + + output_service = output; + threadloop_node_t *apc = thread_loop.GetAPC(); + if (apc) + { + this->secondary_parameters = secondary_parameters; + if (secondary_parameters) + secondary_parameters->Retain(); + + apc->func = APC_Play; + apc->param1 = this; + thread_loop.Schedule(apc); + return NErr_Success; + } + else + return NErr_OutOfMemory; +} + +int PlaybackBase::Playback_SeekSeconds(double seconds) +{ + __android_log_print(ANDROID_LOG_INFO, "libreplicant", "[PlaybackBase] Seek (%f seconds)", seconds); + Agave_Seek *seek = (Agave_Seek *)malloc(sizeof(Agave_Seek)); + if (seek) + { + seek->position_type = AGAVE_PLAYPOSITION_SECONDS; + seek->position.seconds = seconds; + threadloop_node_t *apc = thread_loop.GetAPC(); + if (apc) + { + apc->func = APC_Seek; + apc->param1 = this; + apc->param2 = seek; + thread_loop.Schedule(apc); + return NErr_Success; + } + } + free(seek); + return NErr_OutOfMemory; + +} + +int PlaybackBase::Playback_Pause() +{ + __android_log_print(ANDROID_LOG_INFO, "libreplicant", "[PlaybackBase] Pause"); + threadloop_node_t *apc = thread_loop.GetAPC(); + if (apc) + { + apc->func = APC_Pause; + apc->param1 = this; + thread_loop.Schedule(apc); + return NErr_Success; + } + else + return NErr_OutOfMemory; +} + +int PlaybackBase::Playback_Unpause() +{ + __android_log_print(ANDROID_LOG_INFO, "libreplicant", "[PlaybackBase] Unpause"); + threadloop_node_t *apc = thread_loop.GetAPC(); + if (apc) + { + apc->func = APC_Unpause; + apc->param1 = this; + thread_loop.Schedule(apc); + return NErr_Success; + } + else + return NErr_OutOfMemory; +} + +int PlaybackBase::Playback_Stop() +{ + __android_log_print(ANDROID_LOG_INFO, "libreplicant", "[PlaybackBase] Stop"); + threadloop_node_t *apc = thread_loop.GetAPC(); + if (apc) + { + apc->func = APC_Stop; + apc->param1 = this; + thread_loop.Schedule(apc); + return NErr_Success; + } + else + return NErr_OutOfMemory; +} + +int PlaybackBase::Playback_Close() +{ + __android_log_print(ANDROID_LOG_INFO, "libreplicant", "[PlaybackBase] Close"); + threadloop_node_t *apc = thread_loop.GetAPC(); + if (apc) + { + apc->func = APC_Close; + apc->param1 = this; + thread_loop.Schedule(apc); + return NErr_Success; + } + else + return NErr_OutOfMemory; +} + + +int PlaybackBase::FileLockCallback_Interrupt() +{ + __android_log_print(ANDROID_LOG_INFO, "libreplicant", "[PlaybackBase] Interrupt"); + threadloop_node_t *apc = thread_loop.GetAPC(); + if (apc) + { + apc->func = APC_Interrupt; + apc->param1 = this; + thread_loop.Schedule(apc); + return NErr_Success; + } + else + return NErr_OutOfMemory; +} + +void PlaybackBase::ToggleFlags(int wake_reason) +{ + switch(wake_reason) + { + case WAKE_KILL: + last_wake_flags ^= WAKE_KILL; /* toggle kill flag */ + break; + case WAKE_STOP: + last_wake_flags ^= WAKE_STOP; /* toggle stop flag */ + break; + case WAKE_PLAY: + last_wake_flags ^= WAKE_PLAY; /* toggle play flag */ + break; + case WAKE_PAUSE: + case WAKE_UNPAUSE: + last_wake_flags ^= WAKE_PAUSE; /* toggle pause flag */ + break; + case WAKE_INTERRUPT: + last_wake_flags ^= WAKE_INTERRUPT; /* toggle interrupt flag */ + break; + } +} + +int PlaybackBase::WakeReason(int mask) const +{ + int reason_awoken = last_wake_flags ^ wake_flags; + + reason_awoken = reason_awoken & mask; + + if (reason_awoken & WAKE_INTERRUPT) + { + if (wake_flags & WAKE_INTERRUPT) + return WAKE_INTERRUPT; + else + return WAKE_RESUME; + } + + if (reason_awoken & WAKE_STOP) + { + return WAKE_STOP; + } + + if (reason_awoken & WAKE_KILL) + { + return WAKE_KILL; + } + + if (reason_awoken & WAKE_PLAY) + { + if (wake_flags & WAKE_PLAY) + return WAKE_PLAY; + else /* if someone cleared the play flag for whatever reason, just treat it as a 0 */ + return 0; + } + + if (reason_awoken & WAKE_PAUSE) + { + if (wake_flags & WAKE_PAUSE) + return WAKE_PAUSE; + else + return WAKE_UNPAUSE; + } + + return 0; +} + +int PlaybackBase::Wake(int mask) +{ + assert(mask != 0); /* make sure they didn't specify a 0 mask (which would make this function potentially never return */ + assert((mask & WAKE_ALL_MASK) != 0); /* make sure it's a valid mask */ + + for (;;) + { + int reason_awoken = last_wake_flags ^ wake_flags; + reason_awoken = reason_awoken & mask; + + if (reason_awoken) + { + int ret = WakeReason(mask); + ToggleFlags(ret); // mark the last-known-state of the wake flags + + return ret; + + } + + if (((mask & WAKE_PLAY) && !(wake_flags & WAKE_PLAY))/* if we're stopped and they asked to be woken up for play */ + || ((mask & WAKE_PAUSE) && (wake_flags & WAKE_PAUSE)) /* or waiting to be woken up for unpause */ + || ((mask & WAKE_INTERRUPT) && (wake_flags & WAKE_INTERRUPT))) /* or waiting to be woken up for resume */ + { + thread_loop.Step(); + + int ret = WakeReason(mask); + if (ret) /* if ret is !0, it means we gotten woken up for a reason we care about */ + { + ToggleFlags(ret); // mark the last-known-state of the wake flags + return ret; + } + } + else /* no reason to sleep, so just return 0 (no change) */ + { + return 0; + } + } +} + +int PlaybackBase::Check(int mask) +{ + assert(mask != 0); /* make sure they didn't specify a 0 mask (which would make this function potentially never return */ + assert((mask & WAKE_ALL_MASK) != 0); /* make sure it's a valid mask */ + + int reason_awoken = last_wake_flags ^ wake_flags; + reason_awoken = reason_awoken & mask; + + int ret = 0; + if (reason_awoken) + { + ret = WakeReason(mask); + ToggleFlags(ret); // mark the last-known-state of the wake flags + } + return ret; +} + + +int PlaybackBase::Wait(unsigned int milliseconds, int mask) +{ + int reason_awoken = last_wake_flags ^ wake_flags; + reason_awoken = reason_awoken & mask; + + if (reason_awoken) + { + int ret = WakeReason(mask); + ToggleFlags(ret); // mark the last-known-state of the wake flags + + return ret; + } + + thread_loop.Step(milliseconds); + + int ret = WakeReason(mask); + ToggleFlags(ret); // mark the last-known-state of the wake flags + return ret; +} + +int PlaybackBase::Sleep(unsigned int milliseconds, int mask) +{ + int reason_awoken = last_wake_flags ^ wake_flags; + reason_awoken = reason_awoken & mask; + + if (reason_awoken) + { + int ret = WakeReason(mask); + assert(ret != 0); + + return ret; + + } + + thread_loop.Step(milliseconds); + + int ret = WakeReason(mask); + return ret; + +} + +void PlaybackBase::OnStopPlaying() +{ + // turn off the play flag (also adjust old wake flags so we don't trigger a WAKE_STOP) + __android_log_print(ANDROID_LOG_INFO, "libreplicant", "[PlaybackBase] OnStopPlaying"); + + wake_flags &= ~WAKE_PLAY; + last_wake_flags &= ~WAKE_PLAY; +} + +void PlaybackBase::OnInterrupted() +{ + __android_log_print(ANDROID_LOG_INFO, "libreplicant", "[PlaybackBase] OnInterrupted"); + + wake_flags &= ~WAKE_INTERRUPT; + last_wake_flags &= ~WAKE_INTERRUPT; +} + +Agave_Seek *PlaybackBase::GetSeek() +{ + Agave_Seek *seek = queued_seek; + queued_seek=0; + return seek; +} + +void PlaybackBase::FreeSeek(Agave_Seek *seek) +{ + free(seek); +} + +bool PlaybackBase::PendingSeek() +{ + return !!queued_seek; +} + +void PlaybackBase::APC_Play(void *_playback_base, void *param2, double real_value) +{ + PlaybackBase *playback_base = (PlaybackBase *)_playback_base; + playback_base->wake_flags |= WAKE_PLAY; +} + +void PlaybackBase::APC_Seek(void *_playback_base, void *_seek, double real_value) +{ + PlaybackBase *playback_base = (PlaybackBase *)_playback_base; + Agave_Seek *seek = (Agave_Seek *)_seek; + free(playback_base->queued_seek); + playback_base->queued_seek = seek; + +} + +void PlaybackBase::APC_Pause(void *_playback_base, void *param2, double real_value) +{ + PlaybackBase *playback_base = (PlaybackBase *)_playback_base; + playback_base->wake_flags |= WAKE_PAUSE; +} + +void PlaybackBase::APC_Unpause(void *_playback_base, void *param2, double real_value) +{ + PlaybackBase *playback_base = (PlaybackBase *)_playback_base; + playback_base->wake_flags &= ~WAKE_PAUSE; +} + +void PlaybackBase::APC_Stop(void *_playback_base, void *param2, double real_value) +{ + PlaybackBase *playback_base = (PlaybackBase *)_playback_base; + playback_base->wake_flags |= WAKE_STOP; +} + +void PlaybackBase::APC_Close(void *_playback_base, void *param2, double real_value) +{ + PlaybackBase *playback_base = (PlaybackBase *)_playback_base; + playback_base->wake_flags |= WAKE_KILL; +} + +void PlaybackBase::APC_Interrupt(void *_playback_base, void *param2, double real_value) +{ + PlaybackBase *playback_base = (PlaybackBase *)_playback_base; + playback_base->wake_flags |= WAKE_INTERRUPT; +} diff --git a/Src/replicant/nswasabi/PlaybackBase.h b/Src/replicant/nswasabi/PlaybackBase.h new file mode 100644 index 00000000..8a27387c --- /dev/null +++ b/Src/replicant/nswasabi/PlaybackBase.h @@ -0,0 +1,91 @@ +#pragma once +#include "nx/nx.h" +#include "nu/LockFreeItem.h" +#include "player/ifc_playback.h" +#include "player/ifc_player.h" +#include "player/svc_output.h" +#include "nu/ThreadLoop.h" +#include "filelock/api_filelock.h" + +/* TODO: we can probably redo this without the mutex, possibly using semaphores */ +class PlaybackBase : public ifc_playback, public cb_filelock +{ +public: + using ifc_playback::Retain; + using ifc_playback::Release; + + + /* ifc_playback implementation */ + int WASABICALL Playback_Play(svc_output *output, ifc_playback_parameters *secondary_parameters); + int WASABICALL Playback_SeekSeconds(double seconds); + int WASABICALL Playback_Pause(); + int WASABICALL Playback_Unpause(); + int WASABICALL Playback_Stop(); + int WASABICALL Playback_Close(); + + /* cb_filelock implementation */ + int WASABICALL FileLockCallback_Interrupt(); +protected: + nx_thread_t playback_thread; + svc_output *output_service; + ifc_player *player; + ifc_playback_parameters *secondary_parameters; + nx_uri_t filename; + + enum + { + WAKE_KILL=(1<<0), + WAKE_PLAY=(1<<1), + WAKE_PAUSE=(1<<2), + WAKE_STOP=(1<<3), + WAKE_INTERRUPT=(1<<4), + WAKE_UNPAUSE=(1<<5), // this is actually unused in wake_flags, just used as a return value from Wake/WakeReason + WAKE_RESUME=(1<<6), // this is actually unused in wake_flags, just used as a return value from Wake/WakeReason + WAKE_START_MASK = WAKE_PLAY|WAKE_STOP, + WAKE_KILL_MASK = WAKE_KILL|WAKE_STOP, + WAKE_ALL_MASK = WAKE_KILL|WAKE_PLAY|WAKE_PAUSE|WAKE_STOP|WAKE_INTERRUPT, + }; + + +protected: + PlaybackBase(); + ~PlaybackBase(); + + /* === API for derived classes to use === */ + int Initialize(nx_uri_t filename, ifc_player *player); + int Init(); + + /* this checks if one of the flags in mask has toggled. + if playback is stopped and WAKE_PLAY is in the mask, this will sleep until a flag changes + if playback is paused and WAKE_PAUSE is in the mask, this will sleep until a flag changes + if both WAKE_PLAY and WAKE_PAUSE are in the mask, this will sleep until either happens + returns 0 if no flag changed */ + int Wake(int mask); /* Sleeps indefinitely until a flag changes */ + int Check(int mask); /* like Wake() but never actually goes to sleep for any reason */ + int Wait(unsigned int milliseconds, int mask); /* Sleeps for a limited amount of time for a flag to change */ + int Sleep(unsigned int milliseconds, int mask); /* unlike Wait, this one does *not* update last flags */ + int WakeReason(int mask) const; + + void OnInterrupted(); /* turn off the WAKE_INTERRUPT flag */ + void OnStopPlaying(); /* turn off the WAKE_PLAY flag */ + bool PendingSeek(); + Agave_Seek *GetSeek(); + void FreeSeek(Agave_Seek *seek); + /* === End of API for derived classes to use === */ +private: + void ToggleFlags(int wake_reason); + + int wake_flags; /* marked volatile so the compiler doesn't cache */ + int last_wake_flags; + Agave_Seek *queued_seek; + + ThreadLoop thread_loop; + static void APC_Play(void *_playback_base, void *param2, double real_value); + static void APC_Seek(void *_playback_base, void *param2, double real_value); + static void APC_Pause(void *_playback_base, void *param2, double real_value); + static void APC_Unpause(void *_playback_base, void *param2, double real_value); + static void APC_Stop(void *_playback_base, void *param2, double real_value); + static void APC_Close(void *_playback_base, void *param2, double real_value); + static void APC_Interrupt(void *_playback_base, void *param2, double real_value); + +}; diff --git a/Src/replicant/nswasabi/PlaybackBase2.cpp b/Src/replicant/nswasabi/PlaybackBase2.cpp new file mode 100644 index 00000000..4a924e23 --- /dev/null +++ b/Src/replicant/nswasabi/PlaybackBase2.cpp @@ -0,0 +1,490 @@ +#include "PlaybackBase2.h" + +PlaybackImpl::PlaybackImpl() +{ + playback=0; + playback_parameters=0; +} + +PlaybackImpl::~PlaybackImpl() +{ + // TODO: decide if we need playback->Release() or not + if (playback_parameters) + playback_parameters->Release(); +} + +void PlaybackImpl::Connect(PlaybackBase2 *playback, ifc_playback_parameters *playback_parameters) +{ + this->playback = playback; + // TODO: decide if we need playback->Retain() or not + + this->playback_parameters = playback_parameters; + if (playback_parameters) + playback_parameters->Retain(); + +} + +/* ---------- */ +PlaybackBase2::PlaybackBase2() +{ + out=0; + implementation=0; + filelocker=0; + paused=false; + last_position=0; + output_pointers=0; + exact_length=false; + memset(¶meters, 0, sizeof(parameters)); +} + +PlaybackBase2::~PlaybackBase2() +{ + /* out should have hopefully been close already. just in case */ + if (out) + out->Release(); + out=0; + + if (filelocker) + filelocker->Release(); + delete implementation; + free(output_pointers); + +} + +ns_error_t PlaybackBase2::Initialize(api_service *service_manager, PlaybackImpl *implementation, nx_uri_t filename, ifc_player *player) +{ + service_manager->GetService(&filelocker); + + this->implementation = implementation; + ns_error_t ret = PlaybackBase::Initialize(filename, player); + if (ret != NErr_Success) + return ret; + + implementation->Connect(this, secondary_parameters); + + this->ifc_playback::Retain(); /* the thread needs to hold a reference to this object so that it doesn't disappear out from under us */ + NXThreadCreate(&playback_thread, PlayerThreadFunction, this); + return NErr_Success; +} + +nx_thread_return_t PlaybackBase2::PlayerThreadFunction(nx_thread_parameter_t param) +{ + PlaybackBase2 *playback = (PlaybackBase2 *)param; + NXThreadCurrentSetPriority(NX_THREAD_PRIORITY_PLAYBACK); + nx_thread_return_t ret = playback->DecodeLoop(); + playback->ifc_playback::Release(); /* give up the reference that was acquired before spawning the thread */ + return ret; +} + +int PlaybackBase2::Init() +{ + if (filelocker) + filelocker->WaitForReadInterruptable(filename, this); + + ns_error_t ret = implementation->Open(filename); + if (ret != NErr_Success) + return ret; + + ifc_metadata *metadata; + if (implementation->GetMetadata(&metadata) == NErr_Success) + { + player->SetMetadata(metadata); + metadata->Release(); + } + else + player->SetMetadata(0); + + player->SetSeekable(implementation->IsSeekable()?1:0); + + double length; + ret = implementation->GetLength(&length, &exact_length); + if (ret == NErr_Success) + player->SetLength(length); + + return NErr_Success; +} + +nx_thread_return_t PlaybackBase2::DecodeLoop() +{ + player->OnLoaded(filename); + + int ret = Init(); + if (ret != NErr_Success) + { + implementation->Close(); + if (filelocker) + filelocker->UnlockFile(filename); + player->OnError(ret); + return 0; + } + + player->OnReady(); + + /* wait for Play (or Stop to abort) */ + for (;;) + { + ns_error_t ret = Wake(WAKE_PLAY|WAKE_STOP|WAKE_INTERRUPT); + if (ret == WAKE_PLAY) + { + break; + } + else if (ret == WAKE_STOP) + { + player->OnStopped(); + goto cleanup; + } + else if (ret == WAKE_INTERRUPT) + { + ns_error_t ret = Internal_Interrupt(); + if (ret != NErr_Success) + { + implementation->Close(); + player->OnError(ret); + goto cleanup; + } + } + } + + /* at this point, we know that PLAY is on */ + for (;;) + { + int ret = Check(WAKE_STOP|WAKE_PAUSE|WAKE_INTERRUPT); + if (ret == WAKE_PAUSE) + { + if (out) + out->Pause(1); + paused=true; + continue; /* continue in case there's another wake reason */ + } + else if (ret== WAKE_UNPAUSE) + { + if (out) + out->Pause(0); + paused=false; + continue; /* continue in case there's another wake reason */ + } + else if (ret == WAKE_STOP) + { + if (out) + { + out->Stop(); + out->Release(); + out=0; + } + player->OnStopped(); + goto cleanup; + } + else if (ret == WAKE_INTERRUPT) + { + ns_error_t ret = Internal_Interrupt(); + if (ret != NErr_Success) + { + implementation->Close(); + player->OnError(ret); + goto cleanup; + } + continue; + } + + Agave_Seek *seek = PlaybackBase::GetSeek(); + if (seek) + { + ns_error_t seek_error; + double new_position; + ns_error_t ret = implementation->Seek(seek, &seek_error, &new_position); + if (ret != NErr_Success) + { + player->OnError(ret); + goto cleanup; + } + if (out) + out->Flush(new_position); + player->OnSeekComplete(seek_error, new_position); + PlaybackBase::FreeSeek(seek); + } + + ret = implementation->DecodeStep(); + if (ret == NErr_EndOfFile) + { + if (out) + out->Done(); + + PlaybackBase::OnStopPlaying(); + player->OnEndOfFile(); + + ret = WaitForClose(); + if (out) + out->Release(); + out=0; + + if (ret != NErr_True) + goto cleanup; + } + else if (ret != NErr_Success) + { + if (out) + { + out->Done(); + out->Release(); + out=0; + } + if (ret != NErr_False) + player->OnError(NErr_Error); // TODO: find better error code + goto cleanup; + } + else + { + if (!exact_length) + { + double length; + ret = implementation->GetLength(&length, &exact_length); + if (ret == NErr_Success) + player->SetLength(length); + } + } + } + +cleanup: + implementation->Close(); + + if (filelocker) + filelocker->UnlockFile(filename); + return 0; +} + +ns_error_t PlaybackBase2::WaitForClose() +{ + if (!out) + { + player->OnClosed(); + return NErr_False; + } + else for (;;) + { + int ret = Wait(10, WAKE_PLAY|WAKE_KILL|WAKE_STOP); + if (ret == WAKE_KILL) + { + player->OnClosed(); + return NErr_False; + } + else if (ret == WAKE_PLAY) + { + return NErr_True; + } + else if (ret == WAKE_STOP) + { + player->OnStopped(); + return NErr_False; + } + else + { + if (out->Playing() == NErr_True) + player->SetPosition(last_position - out->Latency()); + else + { + player->SetPosition(last_position); + player->OnClosed(); + return NErr_False; + } + } + } +} + +ns_error_t PlaybackBase2::OpenOutput(const ifc_audioout::Parameters *_parameters) +{ + // if out is already set, it means that there was a change in parameters, so we'll start a new stream + if (out) + { + // check to see that the parameters actually changed + if (!memcmp(¶meters, _parameters, sizeof(parameters))) + return NErr_Success; + + out->Done(); + out=0; + } + + parameters = *_parameters; + + free(output_pointers); + output_pointers = (const uint8_t **)malloc(parameters.audio.number_of_channels * sizeof(const uint8_t *)); + if (!output_pointers) + return NErr_OutOfMemory; + + ns_error_t ret = output_service->AudioOpen(¶meters, player, secondary_parameters, &out); + if (ret != NErr_Success) + { + player->OnError(ret); + return ret; + } + + if (paused) + out->Pause(1); + else + out->Pause(0); + + + return NErr_True; +} + +int PlaybackBase2::OutputNonInterleaved(const void *decode_buffer, size_t decoded, double start_position) +{ + int ret; + size_t frames_written=0; + const uint8_t **buffer = (const uint8_t **)decode_buffer; + + for (size_t c=0;c<parameters.audio.number_of_channels;c++) + { + output_pointers[c] = buffer[c]; + } + + while (decoded) + { + size_t to_write = out->CanWrite(); + if (to_write) + { + if (decoded < to_write) + to_write = decoded; + + ret = out->Output(output_pointers, to_write); + if (ret != NErr_Success) + { + out->Release(); + out=0; + return ret; + } + + decoded -= to_write; + for (size_t c=0;c<parameters.audio.number_of_channels;c++) + { + output_pointers[c] += to_write/parameters.audio.number_of_channels; + } + frames_written += to_write/parameters.audio.number_of_channels; + player->SetPosition(start_position + (double)frames_written/parameters.audio.sample_rate - out->Latency()); + } + else + { + ns_error_t ret = OutputWait(); + if (ret != NErr_Success) + return ret; + } + } + return NErr_True; +} + +int PlaybackBase2::Output(const void *decode_buffer, size_t decoded, double start_position) +{ + int ret; + size_t frames_written=0; + const uint8_t *decode8 = (const uint8_t *)decode_buffer; + size_t buffer_position=0; + while (decoded) + { + size_t to_write = out->CanWrite(); + if (to_write) + { + if (decoded < to_write) + to_write = decoded; + + ret = out->Output(&decode8[buffer_position], to_write); + if (ret != NErr_Success) + { + out->Release(); + out=0; + return ret; + } + + decoded -= to_write; + buffer_position += to_write; + frames_written += to_write/parameters.audio.number_of_channels; + player->SetPosition(start_position + (double)frames_written/parameters.audio.sample_rate - out->Latency()); + } + else + { + ns_error_t ret = OutputWait(); + if (ret != NErr_Success) + return ret; + } + } + return NErr_True; +} + +ns_error_t PlaybackBase2::OutputWait() +{ + if (paused) + { + /* if we're paused, we need to sit and wait until we're eiter unpaused or stopped */ + for (;;) + { + int ret = Wake(WAKE_STOP|WAKE_PAUSE|WAKE_INTERRUPT); + if (ret == WAKE_STOP) + { + out->Stop(); + out->Release(); + out=0; + player->OnStopped(); + return NErr_False; + } + else if (ret == WAKE_UNPAUSE) + { + out->Pause(0); + paused=false; + break; + } + else if (ret == WAKE_PAUSE) + { + out->Pause(1); + paused=true; + } + else if (PlaybackBase::PendingSeek()) + { + return NErr_True; + } + else if (ret == WAKE_INTERRUPT) + { + ns_error_t ret = Internal_Interrupt(); + if (ret != NErr_Success) + return ret; + } + } + } + else + { + int ret = Wait(10, WAKE_STOP); + if (ret == WAKE_STOP) + { + out->Stop(); + out->Release(); + out=0; + player->OnStopped(); + return NErr_False; + } + } + return NErr_Success; +} + +ns_error_t PlaybackBase2::Internal_Interrupt() +{ + Agave_Seek resume_information; + implementation->Interrupt(&resume_information); + ns_error_t ret = filelocker->UnlockFile(filename); + if (ret != NErr_Success) + { + implementation->Close(); + return ret; + } + PlaybackBase::OnInterrupted(); + if (filelocker) + filelocker->WaitForReadInterruptable(filename, this); + ret = implementation->Resume(&resume_information); + + + if (ret != NErr_Success) + return ret; + ifc_metadata *metadata; + if (implementation->GetMetadata(&metadata) == NErr_Success) + { + player->SetMetadata(metadata); + metadata->Release(); + } + return NErr_Success; +}
\ No newline at end of file diff --git a/Src/replicant/nswasabi/PlaybackBase2.h b/Src/replicant/nswasabi/PlaybackBase2.h new file mode 100644 index 00000000..5743aa53 --- /dev/null +++ b/Src/replicant/nswasabi/PlaybackBase2.h @@ -0,0 +1,78 @@ +#pragma once +#include "PlaybackBase.h" +#include "foundation/error.h" +#include "service/api_service.h" +/* this one does most of the work for you. +It assumes blocking I/O and, generally, a simple implementation +You provide an implementation of PlaybackImpl */ + +class PlaybackBase2; + +class PlaybackImpl +{ +public: + void Connect(PlaybackBase2 *playback, ifc_playback_parameters *playback_parameters); + + /* stuff you need to implement */ + + /* destructor. be diligent about closing stuff, can't guarantee that Close() got called beforehand */ + virtual ~PlaybackImpl(); + + virtual ns_error_t Open(nx_uri_t filename)=0; + /* you need to handle the possibility that Close gets called more than one time */ + virtual void Close()=0; + virtual bool IsSeekable()=0; + /* implementation note: add a reference (Retain) before assigning the value */ + virtual ns_error_t GetMetadata(ifc_metadata **metadata)=0; + /* if you set *exact=false, GetLength will get called after the next DecodeStep */ + virtual ns_error_t GetLength(double *length, bool *exact)=0; + /* only return an error if you're in a state you can't recover from. + if you can't seek, then just don't seek and return NErr_Success */ + virtual ns_error_t Seek(const Agave_Seek *seek, ns_error_t *seek_error, double *new_position)=0; + /* return NErr_Success to continue + NErr_EndOfFile to indicate a natural end of file + otherwise return an error */ + virtual ns_error_t DecodeStep()=0; + /* Save information and close the OS file handle. + fill resume_information with whatever information you'll need to resume */ + virtual ns_error_t Interrupt(Agave_Seek *resume_information)=0; + /* During resume, be sure to call player->SetMetadata again */ + virtual ns_error_t Resume(Agave_Seek *resume_information)=0; +protected: + + PlaybackBase2 *playback; + ifc_playback_parameters *playback_parameters; + PlaybackImpl(); + +}; + +class PlaybackBase2 : public PlaybackBase +{ +public: + PlaybackBase2(); + ~PlaybackBase2(); + + ns_error_t Initialize(api_service *service_manager, PlaybackImpl *implementation, nx_uri_t filename, ifc_player *player); + + /* this two should ONLY be called from within your DecodeStep function! */ + ns_error_t OpenOutput(const ifc_audioout::Parameters *parameters); // if this returns an error, return it from DecodeStep() + ns_error_t Output(const void *audio_data, size_t audio_data_length, double begin_position_seconds); // if this returns an error, return it from DecodeStep() + ns_error_t OutputNonInterleaved(const void *audio_data, size_t audio_data_length, double begin_position_seconds); // if this returns an error, return it from DecodeStep() + +private: + static nx_thread_return_t NXTHREADCALL PlayerThreadFunction(nx_thread_parameter_t param); + nx_thread_return_t NXTHREADCALL DecodeLoop(); + ns_error_t Init(); + ns_error_t WaitForClose(); + ns_error_t OutputWait(); + ns_error_t Internal_Interrupt(); + + PlaybackImpl *implementation; + api_filelock *filelocker; + ifc_audioout *out; + bool paused; + double last_position; + bool exact_length; + ifc_audioout::Parameters parameters; + const uint8_t **output_pointers; +};
\ No newline at end of file diff --git a/Src/replicant/nswasabi/ReferenceCounted.h b/Src/replicant/nswasabi/ReferenceCounted.h new file mode 100644 index 00000000..c16b85dc --- /dev/null +++ b/Src/replicant/nswasabi/ReferenceCounted.h @@ -0,0 +1,232 @@ +#pragma once +#include "../foundation/dispatch.h" +#include "../foundation/atomics.h" +#include <new> +#include "../nx/nxstring.h" +#include "../nx/nxuri.h" + +#define REFERENCE_COUNT_AS(x) size_t Retain() { return x::Retain(); } size_t Release() { return x::Release(); } +template <class t> +class ReferenceCounted : public t +{ +public: + ReferenceCounted() { reference_count = 1; } +protected: + /* Dispatchable implementation */ + + size_t WASABICALL Dispatchable_Retain() + { + return nx_atomic_inc(&reference_count); + } + + size_t WASABICALL Dispatchable_Release() + { + if (!reference_count) + return reference_count; + size_t r = nx_atomic_dec(&reference_count); + if (!r) + { +#if defined(__ARM_ARCH_7A__) + __asm__ __volatile__ ("dmb" : : : "memory"); +#endif + delete(this); + } + return r; + } + size_t reference_count; +}; + +template <class t> +class ReferenceCountedBase +{ +public: + ReferenceCountedBase() { reference_count = 1; } + + size_t Retain() + { + return nx_atomic_inc(&reference_count); + } + + size_t Release() + { + if (!reference_count) + return reference_count; + size_t r = nx_atomic_dec(&reference_count); + if (!r) + { +#if defined(__ARM_ARCH_7A__) + __asm__ __volatile__ ("dmb" : : : "memory"); +#endif + delete static_cast<t*>(this); + } + return r; + } + size_t reference_count; +}; + +template <class t> +class ReferenceCountedObject +{ +public: + ReferenceCountedObject() + { + ptr = new (std::nothrow) ReferenceCounted<t>; + }; + + ~ReferenceCountedObject() + { + if (ptr) + ptr->Release(); + } + + operator t *() + { + return ptr; + } + + t *operator ->() + { + return ptr; + } + + t *ptr; +}; + +template <class t> +class ReferenceCountedPointer +{ +public: + ReferenceCountedPointer() + { + ptr = 0; + }; + + ReferenceCountedPointer(t *new_ptr) + { + ptr = new_ptr; + }; + + ~ReferenceCountedPointer() + { + if (ptr) + ptr->Release(); + } + + operator t *() + { + return ptr; + } + + t *operator ->() + { + return ptr; + } + + t **operator &() + { + // if there's something already in here, we need to release it first + if (ptr) + ptr->Release(); + ptr=0; + return &ptr; + } + + t *operator =(t *new_ptr) + { + if (ptr) + ptr->Release(); + ptr=0; + ptr = new_ptr; + return ptr; + } + + t *ptr; +}; + +class ReferenceCountedNXString +{ +public: + ReferenceCountedNXString() + { + ptr = 0; + } + + ReferenceCountedNXString(const ReferenceCountedNXString ©) + { + ptr = NXStringRetain(copy.ptr); + } + + ~ReferenceCountedNXString() + { + NXStringRelease(ptr); + } + + operator nx_string_t() + { + return ptr; + } + + nx_string_t *operator &() + { + // if there's something already in here, we need to release it first + if (ptr) + NXStringRelease(ptr); + ptr=0; + return &ptr; + } + + nx_string_t operator =(nx_string_t new_ptr) + { + if (ptr) + NXStringRelease(ptr); + + ptr = new_ptr; + return ptr; + } + + nx_string_t operator ->() + { + return ptr; + } + + nx_string_t ptr; +}; + +class ReferenceCountedNXURI +{ +public: + ReferenceCountedNXURI() + { + ptr = 0; + } + + ReferenceCountedNXURI(const ReferenceCountedNXURI ©) + { + ptr = NXURIRetain(copy.ptr); + } + + ~ReferenceCountedNXURI() + { + NXURIRelease(ptr); + } + + operator nx_uri_t() + { + return ptr; + } + + nx_uri_t *operator &() + { + // if there's something already in here, we need to release it first + if (ptr) + NXURIRelease(ptr); + ptr=0; + return &ptr; + } + + nx_uri_t operator ->() + { + return ptr; + } + nx_uri_t ptr; +}; diff --git a/Src/replicant/nswasabi/ServiceName.h b/Src/replicant/nswasabi/ServiceName.h new file mode 100644 index 00000000..27b1cf2a --- /dev/null +++ b/Src/replicant/nswasabi/ServiceName.h @@ -0,0 +1,21 @@ +#pragma once +#include "../nx/nxstring.h" + +/* +this is a helper class to implement GetServiceName +just put WASABI_SERVICE_NAME("Your Service Name"); in the public section of your class declaration. +this implementation does leak memory, but I don't think it's that big of a deal (services can't be unloaded at run-time anyway) +if we ever implement the NXSTR() macro, we can eliminate this leak + +e.g. +class MyAPI : public api_whatever +{ +public: + WASABI_SERVICE_NAME("My API"); +}; + */ + +#define WASABI_SERVICE_NAME(x) static nx_string_t GetServiceName(){ static nx_string_t service_name=0; if (!service_name) NXStringCreateWithUTF8(&service_name, x); return NXStringRetain(service_name); } +#define WASABI_SERVICE_GUID(x) static GUID GetServiceGUID() { return x; } + + diff --git a/Src/replicant/nswasabi/VERSION b/Src/replicant/nswasabi/VERSION new file mode 100644 index 00000000..ea710abb --- /dev/null +++ b/Src/replicant/nswasabi/VERSION @@ -0,0 +1 @@ +1.2
\ No newline at end of file diff --git a/Src/replicant/nswasabi/XMLString.cpp b/Src/replicant/nswasabi/XMLString.cpp new file mode 100644 index 00000000..4b22afd5 --- /dev/null +++ b/Src/replicant/nswasabi/XMLString.cpp @@ -0,0 +1,44 @@ +#include "XMLString.h" + +/* TODO: make and use some sort of nx_mutable_string_t object */ +XMLString::XMLString() +{ + data=0; +} + +XMLString::~XMLString() +{ + NXMutableStringDestroy(data); +} + + +void XMLString::Reset() +{ + if (data) + data->nx_string_data->len = 0; // TODO: make mutable string function for this +} + +nx_string_t XMLString::GetString() +{ + // TODO: make mutable string function for this + nx_string_t str = data->nx_string_data; + data->nx_string_data = 0; + NXMutableStringDestroy(data); + data=0; + return str; +} + +void XMLString::XMLCallback_OnStartElement(const nsxml_char_t *xmlpath, const nsxml_char_t *xmltag, ifc_xmlattributes *attributes) +{ + if (data) + data->nx_string_data->len = 0; // TODO: make mutable string function for this +} + +void XMLString::XMLCallback_OnCharacterData(const nsxml_char_t *xmlpath, const nsxml_char_t *xmltag, const nsxml_char_t *characters, size_t num_characters) +{ + if (!data) + data = NXMutableStringCreateFromXML(characters, num_characters); + else + NXMutableStringGrowFromXML(data, characters, num_characters); +} + diff --git a/Src/replicant/nswasabi/XMLString.h b/Src/replicant/nswasabi/XMLString.h new file mode 100644 index 00000000..36d1ef9a --- /dev/null +++ b/Src/replicant/nswasabi/XMLString.h @@ -0,0 +1,21 @@ +#include "xml/ifc_xmlcallback.h" +#include "nx/nxmutablestring.h" + +/* this one is an xml callback that just saves the last encountered string */ + +class XMLString : public ifc_xmlcallback +{ +public: + XMLString(); + ~XMLString(); + void Reset(); + nx_string_t GetString(); + +private: + /* XML callbacks */ + void WASABICALL XMLCallback_OnStartElement(const nsxml_char_t *xmlpath, const nsxml_char_t *xmltag, ifc_xmlattributes *attributes); + void WASABICALL XMLCallback_OnCharacterData(const nsxml_char_t *xmlpath, const nsxml_char_t *xmltag, const nsxml_char_t *characters, size_t num_characters); + + nx_mutable_string_t data; +}; + diff --git a/Src/replicant/nswasabi/nswasabi.sln b/Src/replicant/nswasabi/nswasabi.sln new file mode 100644 index 00000000..d38f0821 --- /dev/null +++ b/Src/replicant/nswasabi/nswasabi.sln @@ -0,0 +1,82 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29509.3 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nswasabi", "nswasabi.vcxproj", "{E4A6C250-B426-4328-945A-303865086F4C}" + ProjectSection(ProjectDependencies) = postProject + {57C90706-B25D-4ACA-9B33-95CDB2427C27} = {57C90706-B25D-4ACA-9B33-95CDB2427C27} + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915} = {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915} + {E105A0A2-7391-47C5-86AC-718003524C3D} = {E105A0A2-7391-47C5-86AC-718003524C3D} + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} = {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nx", "..\nx\nx.vcxproj", "{57C90706-B25D-4ACA-9B33-95CDB2427C27}" + ProjectSection(ProjectDependencies) = postProject + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} = {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nu", "..\nu\nu.vcxproj", "{F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jnetlib", "..\jnetlib\jnetlib.vcxproj", "{E105A0A2-7391-47C5-86AC-718003524C3D}" + ProjectSection(ProjectDependencies) = postProject + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} = {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "..\zlib\zlib.vcxproj", "{0F9730E4-45DA-4BD2-A50A-403A4BC9751A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E4A6C250-B426-4328-945A-303865086F4C}.Debug|Win32.ActiveCfg = Debug|Win32 + {E4A6C250-B426-4328-945A-303865086F4C}.Debug|Win32.Build.0 = Debug|Win32 + {E4A6C250-B426-4328-945A-303865086F4C}.Debug|x64.ActiveCfg = Debug|x64 + {E4A6C250-B426-4328-945A-303865086F4C}.Debug|x64.Build.0 = Debug|x64 + {E4A6C250-B426-4328-945A-303865086F4C}.Release|Win32.ActiveCfg = Release|Win32 + {E4A6C250-B426-4328-945A-303865086F4C}.Release|x64.ActiveCfg = Release|x64 + {E4A6C250-B426-4328-945A-303865086F4C}.Release|x64.Build.0 = Release|x64 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|Win32.ActiveCfg = Debug|Win32 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|Win32.Build.0 = Debug|Win32 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|x64.ActiveCfg = Debug|x64 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|x64.Build.0 = Debug|x64 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|Win32.ActiveCfg = Release|Win32 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|Win32.Build.0 = Release|Win32 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|x64.ActiveCfg = Release|x64 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|x64.Build.0 = Release|x64 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.ActiveCfg = Debug|Win32 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.Build.0 = Debug|Win32 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.ActiveCfg = Debug|x64 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.Build.0 = Debug|x64 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.ActiveCfg = Release|Win32 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.Build.0 = Release|Win32 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.ActiveCfg = Release|x64 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.Build.0 = Release|x64 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.ActiveCfg = Debug|Win32 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.Build.0 = Debug|Win32 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.ActiveCfg = Debug|x64 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.Build.0 = Debug|x64 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.ActiveCfg = Release|Win32 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.Build.0 = Release|Win32 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.ActiveCfg = Release|x64 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.Build.0 = Release|x64 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|Win32.ActiveCfg = Debug|Win32 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|Win32.Build.0 = Debug|Win32 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|x64.ActiveCfg = Debug|x64 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|x64.Build.0 = Debug|x64 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|Win32.ActiveCfg = Release|Win32 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|Win32.Build.0 = Release|Win32 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|x64.ActiveCfg = Release|x64 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C4E799C1-027B-487B-8E1A-31F2D26A1AFE} + EndGlobalSection +EndGlobal diff --git a/Src/replicant/nswasabi/nswasabi.vcxproj b/Src/replicant/nswasabi/nswasabi.vcxproj new file mode 100644 index 00000000..d9d33094 --- /dev/null +++ b/Src/replicant/nswasabi/nswasabi.vcxproj @@ -0,0 +1,159 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup Label="ProjectConfigurations"> + <ProjectConfiguration Include="Debug|Win32"> + <Configuration>Debug</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug|x64"> + <Configuration>Debug</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|Win32"> + <Configuration>Release</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|x64"> + <Configuration>Release</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + </ItemGroup> + <PropertyGroup Label="Globals"> + <ProjectGuid>{E4A6C250-B426-4328-945A-303865086F4C}</ProjectGuid> + <RootNamespace>nswasabi</RootNamespace> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> + <ConfigurationType>StaticLibrary</ConfigurationType> + <PlatformToolset>v142</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> + <ConfigurationType>StaticLibrary</ConfigurationType> + <PlatformToolset>v142</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> + <ConfigurationType>StaticLibrary</ConfigurationType> + <PlatformToolset>v142</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> + <ConfigurationType>StaticLibrary</ConfigurationType> + <PlatformToolset>v142</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <ImportGroup Label="ExtensionSettings"> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <PropertyGroup Label="UserMacros" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <LinkIncremental>true</LinkIncremental> + <OutDir>x86_Debug\</OutDir> + <IntDir>x86_Debug\</IntDir> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <LinkIncremental>true</LinkIncremental> + <OutDir>x64_Debug\</OutDir> + <IntDir>x64_Debug\</IntDir> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <LinkIncremental>false</LinkIncremental> + <OutDir>x86_Release\</OutDir> + <IntDir>x86_Release\</IntDir> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <LinkIncremental>false</LinkIncremental> + <OutDir>x64_Release\</OutDir> + <IntDir>x64_Release\</IntDir> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <ClCompile> + <Optimization>Disabled</Optimization> + <AdditionalIncludeDirectories>..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>EditAndContinue</DebugInformationFormat> + <DisableSpecificWarnings>4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + </ClCompile> + <Lib> + <OutputFile>$(ProjectDir)x86_Debug\$(ProjectName).lib</OutputFile> + </Lib> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <ClCompile> + <Optimization>Disabled</Optimization> + <AdditionalIncludeDirectories>..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN64;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>EditAndContinue</DebugInformationFormat> + <DisableSpecificWarnings>4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + </ClCompile> + <Lib> + <OutputFile>$(ProjectDir)x64_Debug\$(ProjectName).lib</OutputFile> + </Lib> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <ClCompile> + <Optimization>MinSpace</Optimization> + <IntrinsicFunctions>true</IntrinsicFunctions> + <FavorSizeOrSpeed>Size</FavorSizeOrSpeed> + <AdditionalIncludeDirectories>..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN32;NDEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreaded</RuntimeLibrary> + <BufferSecurityCheck>false</BufferSecurityCheck> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + </ClCompile> + <Lib> + <OutputFile>$(ProjectDir)x86_Release\$(ProjectName).lib</OutputFile> + </Lib> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <ClCompile> + <Optimization>MinSpace</Optimization> + <IntrinsicFunctions>true</IntrinsicFunctions> + <FavorSizeOrSpeed>Size</FavorSizeOrSpeed> + <AdditionalIncludeDirectories>..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN64;NDEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreaded</RuntimeLibrary> + <BufferSecurityCheck>false</BufferSecurityCheck> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + </ClCompile> + <Lib> + <OutputFile>$(ProjectDir)x64_Release\$(ProjectName).lib</OutputFile> + </Lib> + </ItemDefinitionGroup> + <ItemGroup> + <ClCompile Include="ApplicationBase.cpp" /> + </ItemGroup> + <ItemGroup> + <ClInclude Include="ApplicationBase.h" /> + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <ImportGroup Label="ExtensionTargets"> + </ImportGroup> +</Project>
\ No newline at end of file diff --git a/Src/replicant/nswasabi/nswasabi.xcodeproj/project.pbxproj b/Src/replicant/nswasabi/nswasabi.xcodeproj/project.pbxproj new file mode 100644 index 00000000..bb8b0ca6 --- /dev/null +++ b/Src/replicant/nswasabi/nswasabi.xcodeproj/project.pbxproj @@ -0,0 +1,499 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXAggregateTarget section */ + 00479F18151C35C300F99D12 /* nswasabi-prepare */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 00479F19151C35C300F99D12 /* Build configuration list for PBXAggregateTarget "nswasabi-prepare" */; + buildPhases = ( + 00479F20151C3CAE00F99D12 /* Generate Version Info */, + ); + dependencies = ( + 0039B375152A1F7100D96D3E /* PBXTargetDependency */, + ); + name = "nswasabi-prepare"; + productName = "nswasabi-version"; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 00479F10151C33FE00F99D12 /* XMLString.h in Headers */ = {isa = PBXBuildFile; fileRef = 00B73470151C308A00A8251C /* XMLString.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 00479F22151C3D1D00F99D12 /* version.h in Headers */ = {isa = PBXBuildFile; fileRef = 00479F21151C3D1D00F99D12 /* version.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 00B73471151C308A00A8251C /* AutoCharNX.h in Headers */ = {isa = PBXBuildFile; fileRef = 00B7346A151C308A00A8251C /* AutoCharNX.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 00B73472151C308A00A8251C /* ObjectFactory.h in Headers */ = {isa = PBXBuildFile; fileRef = 00B7346B151C308A00A8251C /* ObjectFactory.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 00B73473151C308A00A8251C /* ReferenceCounted.h in Headers */ = {isa = PBXBuildFile; fileRef = 00B7346C151C308A00A8251C /* ReferenceCounted.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 00B73474151C308A00A8251C /* ServiceName.h in Headers */ = {isa = PBXBuildFile; fileRef = 00B7346D151C308A00A8251C /* ServiceName.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 00B73475151C308A00A8251C /* singleton.h in Headers */ = {isa = PBXBuildFile; fileRef = 00B7346E151C308A00A8251C /* singleton.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 00C27E9A15372DEA008D95CD /* precomp.h in Headers */ = {isa = PBXBuildFile; fileRef = 00C27E9915372DEA008D95CD /* precomp.h */; }; + 0CC9987C14C5DC6900484291 /* PlaybackBase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0CC9987A14C5DC6900484291 /* PlaybackBase.cpp */; }; + 0CC9987D14C5DC6900484291 /* PlaybackBase.h in Headers */ = {isa = PBXBuildFile; fileRef = 0CC9987B14C5DC6900484291 /* PlaybackBase.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B104AEA614C7CC5100211271 /* ApplicationBase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B104AEA414C7CC5100211271 /* ApplicationBase.cpp */; }; + B104AEA714C7CC5100211271 /* ApplicationBase.h in Headers */ = {isa = PBXBuildFile; fileRef = B104AEA514C7CC5100211271 /* ApplicationBase.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B15A069D14F499F9004F70E7 /* ID3v1Metadata.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B15A069914F499F9004F70E7 /* ID3v1Metadata.cpp */; }; + B15A069E14F499F9004F70E7 /* ID3v1Metadata.h in Headers */ = {isa = PBXBuildFile; fileRef = B15A069A14F499F9004F70E7 /* ID3v1Metadata.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B15A069F14F499F9004F70E7 /* ID3v2Metadata.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B15A069B14F499F9004F70E7 /* ID3v2Metadata.cpp */; }; + B15A06A014F499F9004F70E7 /* ID3v2Metadata.h in Headers */ = {isa = PBXBuildFile; fileRef = B15A069C14F499F9004F70E7 /* ID3v2Metadata.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B193DFDA14F49B6F005D99FB /* APEv2Metadata.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B193DFD814F49B6F005D99FB /* APEv2Metadata.cpp */; }; + B193DFDB14F49B6F005D99FB /* APEv2Metadata.h in Headers */ = {isa = PBXBuildFile; fileRef = B193DFD914F49B6F005D99FB /* APEv2Metadata.h */; settings = {ATTRIBUTES = (Public, ); }; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 0039B374152A1F7100D96D3E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0CC9986414C5DC3C00484291 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 00479F14151C35B700F99D12; + remoteInfo = "nswasabi-cleanup"; + }; + 00479F1C151C3C4000F99D12 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0CC9986414C5DC3C00484291 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 00479F14151C35B700F99D12; + remoteInfo = "nswasabi-cleanup"; + }; + 00479F1E151C3C4300F99D12 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0CC9986414C5DC3C00484291 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 00479F18151C35C300F99D12; + remoteInfo = "nswasabi-version"; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 00479F21151C3D1D00F99D12 /* version.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = version.h; path = $PROJECT_DERIVED_FILE_DIR/version.h; sourceTree = "<absolute>"; }; + 00B7346A151C308A00A8251C /* AutoCharNX.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AutoCharNX.h; sourceTree = "<group>"; }; + 00B7346B151C308A00A8251C /* ObjectFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ObjectFactory.h; sourceTree = "<group>"; }; + 00B7346C151C308A00A8251C /* ReferenceCounted.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ReferenceCounted.h; sourceTree = "<group>"; }; + 00B7346D151C308A00A8251C /* ServiceName.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ServiceName.h; sourceTree = "<group>"; }; + 00B7346E151C308A00A8251C /* singleton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = singleton.h; sourceTree = "<group>"; }; + 00B7346F151C308A00A8251C /* XMLString.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = XMLString.cpp; sourceTree = "<group>"; }; + 00B73470151C308A00A8251C /* XMLString.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMLString.h; sourceTree = "<group>"; }; + 00B7347A151C309D00A8251C /* VERSION */ = {isa = PBXFileReference; lastKnownFileType = text; path = VERSION; sourceTree = "<group>"; }; + 00C27E9915372DEA008D95CD /* precomp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = precomp.h; sourceTree = "<group>"; }; + 0CC9986D14C5DC3C00484291 /* libnswasabi.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libnswasabi.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 0CC9987A14C5DC6900484291 /* PlaybackBase.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PlaybackBase.cpp; sourceTree = "<group>"; }; + 0CC9987B14C5DC6900484291 /* PlaybackBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PlaybackBase.h; sourceTree = "<group>"; }; + B104AEA414C7CC5100211271 /* ApplicationBase.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ApplicationBase.cpp; sourceTree = "<group>"; }; + B104AEA514C7CC5100211271 /* ApplicationBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ApplicationBase.h; sourceTree = "<group>"; }; + B15A069914F499F9004F70E7 /* ID3v1Metadata.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ID3v1Metadata.cpp; sourceTree = "<group>"; }; + B15A069A14F499F9004F70E7 /* ID3v1Metadata.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ID3v1Metadata.h; sourceTree = "<group>"; }; + B15A069B14F499F9004F70E7 /* ID3v2Metadata.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ID3v2Metadata.cpp; sourceTree = "<group>"; }; + B15A069C14F499F9004F70E7 /* ID3v2Metadata.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ID3v2Metadata.h; sourceTree = "<group>"; }; + B193DFD814F49B6F005D99FB /* APEv2Metadata.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = APEv2Metadata.cpp; sourceTree = "<group>"; }; + B193DFD914F49B6F005D99FB /* APEv2Metadata.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = APEv2Metadata.h; sourceTree = "<group>"; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 0CC9986A14C5DC3C00484291 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 00B73479151C308E00A8251C /* Version */ = { + isa = PBXGroup; + children = ( + 00B7347A151C309D00A8251C /* VERSION */, + 00479F21151C3D1D00F99D12 /* version.h */, + ); + name = Version; + sourceTree = "<group>"; + }; + 0CC9986214C5DC3C00484291 = { + isa = PBXGroup; + children = ( + 00C27E9915372DEA008D95CD /* precomp.h */, + B193DFD814F49B6F005D99FB /* APEv2Metadata.cpp */, + B193DFD914F49B6F005D99FB /* APEv2Metadata.h */, + B104AEA414C7CC5100211271 /* ApplicationBase.cpp */, + B104AEA514C7CC5100211271 /* ApplicationBase.h */, + 00B7346A151C308A00A8251C /* AutoCharNX.h */, + B15A069914F499F9004F70E7 /* ID3v1Metadata.cpp */, + B15A069A14F499F9004F70E7 /* ID3v1Metadata.h */, + B15A069B14F499F9004F70E7 /* ID3v2Metadata.cpp */, + B15A069C14F499F9004F70E7 /* ID3v2Metadata.h */, + 00B7346B151C308A00A8251C /* ObjectFactory.h */, + 0CC9987A14C5DC6900484291 /* PlaybackBase.cpp */, + 0CC9987B14C5DC6900484291 /* PlaybackBase.h */, + 00B7346C151C308A00A8251C /* ReferenceCounted.h */, + 00B7346D151C308A00A8251C /* ServiceName.h */, + 00B7346E151C308A00A8251C /* singleton.h */, + 00B7346F151C308A00A8251C /* XMLString.cpp */, + 00B73470151C308A00A8251C /* XMLString.h */, + 00B73479151C308E00A8251C /* Version */, + 0CC9986E14C5DC3C00484291 /* Products */, + ); + sourceTree = "<group>"; + }; + 0CC9986E14C5DC3C00484291 /* Products */ = { + isa = PBXGroup; + children = ( + 0CC9986D14C5DC3C00484291 /* libnswasabi.a */, + ); + name = Products; + sourceTree = "<group>"; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 0CC9986B14C5DC3C00484291 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 0CC9987D14C5DC6900484291 /* PlaybackBase.h in Headers */, + B104AEA714C7CC5100211271 /* ApplicationBase.h in Headers */, + B15A069E14F499F9004F70E7 /* ID3v1Metadata.h in Headers */, + B15A06A014F499F9004F70E7 /* ID3v2Metadata.h in Headers */, + B193DFDB14F49B6F005D99FB /* APEv2Metadata.h in Headers */, + 00B73471151C308A00A8251C /* AutoCharNX.h in Headers */, + 00B73472151C308A00A8251C /* ObjectFactory.h in Headers */, + 00B73473151C308A00A8251C /* ReferenceCounted.h in Headers */, + 00B73474151C308A00A8251C /* ServiceName.h in Headers */, + 00B73475151C308A00A8251C /* singleton.h in Headers */, + 00479F10151C33FE00F99D12 /* XMLString.h in Headers */, + 00479F22151C3D1D00F99D12 /* version.h in Headers */, + 00C27E9A15372DEA008D95CD /* precomp.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXLegacyTarget section */ + 00479F14151C35B700F99D12 /* nswasabi-cleanup */ = { + isa = PBXLegacyTarget; + buildArgumentsString = "$(NSBUILD_TOOLS_BIN_DIR)/cleanbuild --xcode-mode --libraries \"$(LIBRARY_PATH)\" \"$(PUBLIC_HEADERS_DIR)\" \"$(DWARF_DSYM_PATH)\" \"$(PROJECT_DERIVED_FILE_DIR)/version.*\""; + buildConfigurationList = 00479F15151C35B700F99D12 /* Build configuration list for PBXLegacyTarget "nswasabi-cleanup" */; + buildPhases = ( + ); + buildToolPath = /bin/sh; + buildWorkingDirectory = ""; + dependencies = ( + ); + name = "nswasabi-cleanup"; + passBuildSettingsInEnvironment = 1; + productName = "nswasabi-cleanup"; + }; +/* End PBXLegacyTarget section */ + +/* Begin PBXNativeTarget section */ + 0CC9986C14C5DC3C00484291 /* nswasabi */ = { + isa = PBXNativeTarget; + buildConfigurationList = 0CC9987114C5DC3C00484291 /* Build configuration list for PBXNativeTarget "nswasabi" */; + buildPhases = ( + 0CC9986914C5DC3C00484291 /* Sources */, + 0CC9986A14C5DC3C00484291 /* Frameworks */, + 0CC9986B14C5DC3C00484291 /* Headers */, + 00479F13151C354F00F99D12 /* Install Public Headers */, + ); + buildRules = ( + ); + dependencies = ( + 00479F1D151C3C4000F99D12 /* PBXTargetDependency */, + 00479F1F151C3C4300F99D12 /* PBXTargetDependency */, + ); + name = nswasabi; + productName = nswasabi; + productReference = 0CC9986D14C5DC3C00484291 /* libnswasabi.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 0CC9986414C5DC3C00484291 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0500; + ORGANIZATIONNAME = "Nullsoft, Inc."; + }; + buildConfigurationList = 0CC9986714C5DC3C00484291 /* Build configuration list for PBXProject "nswasabi" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 0CC9986214C5DC3C00484291; + productRefGroup = 0CC9986E14C5DC3C00484291 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 0CC9986C14C5DC3C00484291 /* nswasabi */, + 00479F14151C35B700F99D12 /* nswasabi-cleanup */, + 00479F18151C35C300F99D12 /* nswasabi-prepare */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXShellScriptBuildPhase section */ + 00479F13151C354F00F99D12 /* Install Public Headers */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 8; + files = ( + ); + inputPaths = ( + "$(BUILT_PRODUCTS_DIR)$(PUBLIC_HEADERS_FOLDER_PATH)", + ); + name = "Install Public Headers"; + outputPaths = ( + "$(DSTROOT)$(PUBLIC_HEADERS_FOLDER_PATH)", + ); + runOnlyForDeploymentPostprocessing = 1; + shellPath = /bin/sh; + shellScript = "INSTALLTOOL=\"$NSBUILD_TOOLS_BIN_DIR/installtool\"\n$INSTALLTOOL --headers-only \\\n \"$SCRIPT_INPUT_FILE_0/\" \\\n \"$SCRIPT_OUTPUT_FILE_0\"\n"; + showEnvVarsInLog = 0; + }; + 00479F20151C3CAE00F99D12 /* Generate Version Info */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "$(SRCROOT)/VERSION", + "$(NSBUILD_TOOLS_SHARE_DIR)/nvgtool/lib-version.template.h", + ); + name = "Generate Version Info"; + outputPaths = ( + "$(PROJECT_DERIVED_FILE_DIR)/version.h", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "PRODUCT_VERSION=$(cat \"$SCRIPT_INPUT_FILE_0\")\n\nif [ ! -d \"$PROJECT_DERIVED_FILE_DIR\" ]; then\n mkdir -p \"$PROJECT_DERIVED_FILE_DIR\"\nfi\n\nNVGTOOL=\"$NSBUILD_TOOLS_BIN_DIR/nvgtool\"\n$NVGTOOL --product-name \"$PRODUCT_NAME\" \\\n --product-version \"$PRODUCT_VERSION\" \\\n --input-file \"$SCRIPT_INPUT_FILE_1\" \\\n --output-file \"$SCRIPT_OUTPUT_FILE_0\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 0CC9986914C5DC3C00484291 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0CC9987C14C5DC6900484291 /* PlaybackBase.cpp in Sources */, + B104AEA614C7CC5100211271 /* ApplicationBase.cpp in Sources */, + B15A069D14F499F9004F70E7 /* ID3v1Metadata.cpp in Sources */, + B15A069F14F499F9004F70E7 /* ID3v2Metadata.cpp in Sources */, + B193DFDA14F49B6F005D99FB /* APEv2Metadata.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 0039B375152A1F7100D96D3E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 00479F14151C35B700F99D12 /* nswasabi-cleanup */; + targetProxy = 0039B374152A1F7100D96D3E /* PBXContainerItemProxy */; + }; + 00479F1D151C3C4000F99D12 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 00479F14151C35B700F99D12 /* nswasabi-cleanup */; + targetProxy = 00479F1C151C3C4000F99D12 /* PBXContainerItemProxy */; + }; + 00479F1F151C3C4300F99D12 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 00479F18151C35C300F99D12 /* nswasabi-prepare */; + targetProxy = 00479F1E151C3C4300F99D12 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 00479F16151C35B700F99D12 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + DWARF_DSYM_PATH = "$(BUILT_PRODUCTS_DIR)/$(EXECUTABLE_NAME).dSYM"; + LIBRARY_PATH = "$(BUILT_PRODUCTS_DIR)/$(EXECUTABLE_NAME)"; + PUBLIC_HEADERS_DIR = "$(BUILT_PRODUCTS_DIR)$(PUBLIC_HEADERS_FOLDER_PATH)"; + }; + name = Debug; + }; + 00479F17151C35B700F99D12 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + DWARF_DSYM_PATH = "$(BUILT_PRODUCTS_DIR)/$(EXECUTABLE_NAME).dSYM"; + LIBRARY_PATH = "$(BUILT_PRODUCTS_DIR)/$(EXECUTABLE_NAME)"; + PUBLIC_HEADERS_DIR = "$(BUILT_PRODUCTS_DIR)$(PUBLIC_HEADERS_FOLDER_PATH)"; + }; + name = Release; + }; + 00479F1A151C35C300F99D12 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + }; + name = Debug; + }; + 00479F1B151C35C300F99D12 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + }; + name = Release; + }; + 0CC9986F14C5DC3C00484291 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DYLIB_COMPATIBILITY_VERSION = "$(CURRENT_PROJECT_VERSION)"; + DYLIB_CURRENT_VERSION = "$(CURRENT_PROJECT_VERSION)"; + EXECUTABLE_EXTENSION = a; + EXECUTABLE_NAME = "$(EXECUTABLE_PREFIX)$(PRODUCT_NAME).$(EXECUTABLE_EXTENSION)"; + EXECUTABLE_PREFIX = lib; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_INCREASE_PRECOMPILED_HEADER_SHARING = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = precomp.h; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_VERSION = com.apple.compilers.llvm.clang.1_0; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INSTALL_PATH = "$(INSTALL_PATH_PREFIX)/lib"; + INSTALL_PATH_PREFIX = /usr/local; + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.6; + NSBUILD_TOOLS_BIN_DIR = "$(NSBUILD_TOOLS_DIR)/bin"; + NSBUILD_TOOLS_DIR = "$(SRCROOT)/../../build-tools"; + NSBUILD_TOOLS_SHARE_DIR = "$(NSBUILD_TOOLS_DIR)/share"; + ONLY_ACTIVE_ARCH = YES; + PRIVATE_HEADERS_FOLDER_PATH = "$(INSTALL_PATH_PREFIX)/include/$(PRODUCT_NAME)"; + PRODUCT_NAME = "$(PROJECT_NAME)"; + PUBLIC_HEADERS_FOLDER_PATH = "$(INSTALL_PATH_PREFIX)/include/$(PRODUCT_NAME)"; + SDKROOT = macosx; + USER_HEADER_SEARCH_PATHS = ".. $(BUILT_PRODUCTS_DIR)$(INSTALL_PATH_PREFIX)/include"; + }; + name = Debug; + }; + 0CC9987014C5DC3C00484291 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DYLIB_COMPATIBILITY_VERSION = "$(CURRENT_PROJECT_VERSION)"; + DYLIB_CURRENT_VERSION = "$(CURRENT_PROJECT_VERSION)"; + EXECUTABLE_EXTENSION = a; + EXECUTABLE_NAME = "$(EXECUTABLE_PREFIX)$(PRODUCT_NAME).$(EXECUTABLE_EXTENSION)"; + EXECUTABLE_PREFIX = lib; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_INCREASE_PRECOMPILED_HEADER_SHARING = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = precomp.h; + GCC_VERSION = com.apple.compilers.llvm.clang.1_0; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INSTALL_PATH = "$(INSTALL_PATH_PREFIX)/lib"; + INSTALL_PATH_PREFIX = /usr/local; + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.6; + NSBUILD_TOOLS_BIN_DIR = "$(NSBUILD_TOOLS_DIR)/bin"; + NSBUILD_TOOLS_DIR = "$(SRCROOT)/../../build-tools"; + NSBUILD_TOOLS_SHARE_DIR = "$(NSBUILD_TOOLS_DIR)/share"; + PRIVATE_HEADERS_FOLDER_PATH = "$(INSTALL_PATH_PREFIX)/include/$(PRODUCT_NAME)"; + PRODUCT_NAME = "$(PROJECT_NAME)"; + PUBLIC_HEADERS_FOLDER_PATH = "$(INSTALL_PATH_PREFIX)/include/$(PRODUCT_NAME)"; + SDKROOT = macosx; + USER_HEADER_SEARCH_PATHS = ".. $(BUILT_PRODUCTS_DIR)$(INSTALL_PATH_PREFIX)/include"; + }; + name = Release; + }; + 0CC9987214C5DC3C00484291 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + }; + name = Debug; + }; + 0CC9987314C5DC3C00484291 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 00479F15151C35B700F99D12 /* Build configuration list for PBXLegacyTarget "nswasabi-cleanup" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 00479F16151C35B700F99D12 /* Debug */, + 00479F17151C35B700F99D12 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 00479F19151C35C300F99D12 /* Build configuration list for PBXAggregateTarget "nswasabi-prepare" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 00479F1A151C35C300F99D12 /* Debug */, + 00479F1B151C35C300F99D12 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 0CC9986714C5DC3C00484291 /* Build configuration list for PBXProject "nswasabi" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0CC9986F14C5DC3C00484291 /* Debug */, + 0CC9987014C5DC3C00484291 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 0CC9987114C5DC3C00484291 /* Build configuration list for PBXNativeTarget "nswasabi" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0CC9987214C5DC3C00484291 /* Debug */, + 0CC9987314C5DC3C00484291 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 0CC9986414C5DC3C00484291 /* Project object */; +} diff --git a/Src/replicant/nswasabi/nswasabi.xcodeproj/xcshareddata/xcschemes/nswasabi.xcscheme b/Src/replicant/nswasabi/nswasabi.xcodeproj/xcshareddata/xcschemes/nswasabi.xcscheme new file mode 100644 index 00000000..84c4002b --- /dev/null +++ b/Src/replicant/nswasabi/nswasabi.xcodeproj/xcshareddata/xcschemes/nswasabi.xcscheme @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Scheme + LastUpgradeVersion = "0500" + version = "1.3"> + <BuildAction + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> + <BuildActionEntries> + <BuildActionEntry + buildForTesting = "YES" + buildForRunning = "YES" + buildForProfiling = "YES" + buildForArchiving = "YES" + buildForAnalyzing = "YES"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "0CC9986C14C5DC3C00484291" + BuildableName = "libnswasabi.a" + BlueprintName = "nswasabi" + ReferencedContainer = "container:nswasabi.xcodeproj"> + </BuildableReference> + </BuildActionEntry> + </BuildActionEntries> + </BuildAction> + <TestAction + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + shouldUseLaunchSchemeArgsEnv = "YES" + buildConfiguration = "Debug"> + <Testables> + </Testables> + </TestAction> + <LaunchAction + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + launchStyle = "0" + useCustomWorkingDirectory = "NO" + buildConfiguration = "Debug" + ignoresPersistentStateOnLaunch = "NO" + debugDocumentVersioning = "YES" + allowLocationSimulation = "YES"> + <AdditionalOptions> + </AdditionalOptions> + </LaunchAction> + <ProfileAction + shouldUseLaunchSchemeArgsEnv = "YES" + savedToolIdentifier = "" + useCustomWorkingDirectory = "NO" + buildConfiguration = "Release" + debugDocumentVersioning = "YES"> + </ProfileAction> + <AnalyzeAction + buildConfiguration = "Debug"> + </AnalyzeAction> + <ArchiveAction + buildConfiguration = "Release" + revealArchiveInOrganizer = "YES"> + </ArchiveAction> +</Scheme> diff --git a/Src/replicant/nswasabi/precomp.h b/Src/replicant/nswasabi/precomp.h new file mode 100644 index 00000000..955fa471 --- /dev/null +++ b/Src/replicant/nswasabi/precomp.h @@ -0,0 +1,32 @@ +// +// precomp.h +// nswasabi +// + +#include <assert.h> +#include <limits.h> +#include <new> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "foundation/error.h" +#include "foundation/types.h" + +#include "nu/ByteReader.h" +#include "nu/ByteWriter.h" +#include "nu/LockFreeItem.h" +#include "nu/ThreadLoop.h" + +#include "nx/nxcondition.h" +#include "nx/nxstring.h" +#include "nx/nxuri.h" +#include "nx/nxmutablestring.h" + +#include "metadata/metadata.h" +#include "metadata/MetadataKeys.h" + +#include "nsid3v1/nsid3v1.h" +#include "nsid3v2/nsid3v2.h" + +#include "service/ifc_servicefactory.h" diff --git a/Src/replicant/nswasabi/singleton.h b/Src/replicant/nswasabi/singleton.h new file mode 100644 index 00000000..b31a68bc --- /dev/null +++ b/Src/replicant/nswasabi/singleton.h @@ -0,0 +1,117 @@ +#pragma once + +#include "service/ifc_servicefactory.h" + +/* +====== Usage ====== +disp_t: your Dispatchable base class +implt_t: your implementation class + +SingletonServiceFactory<disp_t, impl_t> myFactory; +impl_t myImplementation; + +//.... + +//during service registration +myFactory.Register(WASABI2_API_SVC, &myImplementation); + +//during service deregistration +myFactory.Deregister(WASABI2_API_SVC, &myImplementation); + +==== Class requirements ==== +your base or implementation class requires the following three static methods +static FOURCC getServiceType(); // return your type (e.g. WaSvc::UNIQUE)... might already be defined in the dispatchable base class +static const char *getServiceName(); // return your service name +static GUID getServiceGuid(); // return your service GUID +*/ + +class WasabiServiceFactory +{ +public: + virtual ~WasabiServiceFactory() {} + virtual void Register(api_service *serviceManager)=0; + virtual void Deregister(api_service *serviceManager)=0; +}; + + +template <class impl_t, class disp_t> +class SingletonServiceFactory : public ifc_serviceFactory +{ +public: + SingletonServiceFactory() : impl(0) + { + } + + ~SingletonServiceFactory() + { + } + + void Register(api_service *serviceManager, impl_t *new_impl) + { + impl=new_impl; + serviceManager->Register(this); + } + + void Deregister(api_service *serviceManager) + { + if (impl) + { + serviceManager->Unregister(this); + impl=0; + } + } + +private: + GUID WASABICALL ServiceFactory_GetServiceType() { return impl_t::GetServiceType(); } + nx_string_t WASABICALL ServiceFactory_GetServiceName() { return impl_t::GetServiceName(); } + GUID WASABICALL ServiceFactory_GetGUID() { return impl_t::GetServiceGUID(); } // GUID per service factory, can be INVALID_GUID + void *WASABICALL ServiceFactory_GetInterface() { return static_cast<disp_t *>(impl); } + int WASABICALL ServiceFactory_ServiceNotify(int msg, intptr_t param1 = 0, intptr_t param2 = 0) { return 0; } + +private: + impl_t *impl; + +}; + +/* same as above, but this one also constructs the implementation object itself + useful when: + 1) you don't need to ever access the implementation object yourself + 2) the implementation class requires no constructor parameters + + TODO: might want to change this to "new" the object during Register and "delete" during Deregister, + in case destruction during program termination isn't safe. +*/ +template <class impl_t, class disp_t> +class SingletonService : public ifc_serviceFactory, public WasabiServiceFactory +{ +public: + SingletonService() + { + } + + ~SingletonService() + { + } + + void Register(api_service *serviceManager) + { + serviceManager->Register(this); + } + + void Deregister(api_service *serviceManager) + { + serviceManager->Unregister(this); + } + +private: + GUID WASABICALL ServiceFactory_GetServiceType() { return impl_t::GetServiceType(); } + nx_string_t WASABICALL ServiceFactory_GetServiceName() { return impl_t::GetServiceName(); } + GUID WASABICALL ServiceFactory_GetGUID() { return impl_t::GetServiceGUID(); } // GUID per service factory, can be INVALID_GUID + void *WASABICALL ServiceFactory_GetInterface() { return static_cast<disp_t *>(&impl); } + int WASABICALL ServiceFactory_ServiceNotify(int msg, intptr_t param1 = 0, intptr_t param2 = 0) { return 0; } + +private: + impl_t impl; + +}; + |