Пример #1
0
double UserObjectImp::toNumber(ExecState *exec) const
{
    double result = 0;
    JSUserObject* jsObjPtr = KJSValueToJSObject(toObject(exec), exec);
    CFTypeRef cfValue = jsObjPtr ? jsObjPtr->CopyCFValue() : 0;
    if (cfValue)
    {
        CFTypeID cfType = CFGetTypeID(cfValue);

        if (cfValue == GetCFNull())
        {
            //
        }
        else if (cfType == CFBooleanGetTypeID())
        {
            if (cfValue == kCFBooleanTrue)
            {
                result = 1;
            }
        }
        else if (cfType == CFStringGetTypeID())
        {
            result = CFStringGetDoubleValue((CFStringRef)cfValue);
        }
        else if (cfType == CFNumberGetTypeID())
        {
            CFNumberGetValue((CFNumberRef)cfValue, kCFNumberDoubleType, &result);
        }
    }
    ReleaseCFType(cfValue);
    if (jsObjPtr) jsObjPtr->Release();
    return result;
}
static void asynchttp_complete(asynchttp_t *http) {
    secdebug("http", "http: %p", http);
    /* Shutdown streams and timer, we're about to invoke our client callback. */
    if (http->stream) {
        CFReadStreamSetClient(http->stream, kCFStreamEventNone, NULL, NULL);
        CFReadStreamSetDispatchQueue(http->stream, NULL);
        CFReadStreamClose(http->stream);
        CFReleaseNull(http->stream);
    }
    if (http->timer) {
        dispatch_source_cancel(http->timer);
        dispatch_release_null(http->timer);
    }

    if (http->completed) {
        /* This should probably move to our clients. */
        CFTimeInterval maxAge = NULL_TIME;
        if (http->response) {
            CFStringRef cacheControl = CFHTTPMessageCopyHeaderFieldValue(
                http->response, CFSTR("cache-control"));
            if (cacheControl) {
                CFStringRef maxAgeValue = copyParseMaxAge(cacheControl);
                CFRelease(cacheControl);
                if (maxAgeValue) {
                    secdebug("http", "http header max-age: %@", maxAgeValue);
                    maxAge = CFStringGetDoubleValue(maxAgeValue);
                    CFRelease(maxAgeValue);
                }
            }
        }
        http->completed(http, maxAge);
    }
}
Пример #3
0
//--------------------------------------------------------------------------------
OSStatus SubSynth::GetProperty(AudioUnitPropertyID inPropertyID, AudioUnitScope inScope, AudioUnitElement inElement, void * outData)
{
	if (inScope == kAudioUnitScope_Global)
	{
		switch (inPropertyID)
		{
			case kAudioUnitProperty_ParameterValueFromString:
			{
				AudioUnitParameterValueFromString * name = (AudioUnitParameterValueFromString*)outData;
				if (name->inString == NULL)
					return kAudioUnitErr_InvalidPropertyValue;
				double paramValue_literal = CFStringGetDoubleValue(name->inString);
				switch (name->inParamID)
				{
					case kParam_Tune:
						if (paramValue_literal <= 0.0)
							name->outValue = 0.0;	// XXX avoid log10(0) or log10(-X)
						else
							name->outValue = (log10(paramValue_literal / (0.0726 * GetSampleRate())) + 2.5) / 1.5;
						break;
					case kParam_Release:
						return kAudioUnitErr_PropertyNotInUse;	// XXX I can't figure out how to invert this one
					default:
						return kAudioUnitErr_InvalidParameter;
				}
				return noErr;
			}

			case kAudioUnitProperty_ParameterStringFromValue:
			{
				AudioUnitParameterStringFromValue * name = (AudioUnitParameterStringFromValue*)outData;
				double paramValue = (name->inValue == NULL) ? GetParameter(name->inParamID) : *(name->inValue);
				int precision = 0;
				switch (name->inParamID)
				{
					case kParam_Tune:
						paramValue = 0.0726 * GetSampleRate() * pow(10.0, -2.5 + (1.5 * paramValue));
						precision = 3;
						break;
					case kParam_Release:
						paramValue = GetReleaseTimeForParamValue(paramValue);
						precision = 1;
						break;
					default:
						return kAudioUnitErr_InvalidParameter;
				}
				name->outString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%.*f"), precision, paramValue);
				if (name->outString == NULL)
					return coreFoundationUnknownErr;
				return noErr;
			}
		}
	}

	return AUEffectBase::GetProperty(inPropertyID, inScope, inElement, outData);
}
/* ------------------------------------------------------------------------------------------------------

SetNumberValueFromHeader: read FITS header and set number value to attribute name.

------------------------------------------------------------------------------------------------------ */
void SetNumberValueFromFITSHeader(const char* filename, 
				CFMutableDictionaryRef attributes, 
				const char* importerAttrName, 
				char* keyword, 
				Boolean isCoordinates)
{
	CFStringRef headerValue = getCleanedHeaderValue(filename, keyword);
	
	if (headerValue != NULL) {
		double number;
		CFRange r = CFStringFind(headerValue, CFSTR(":"), 0);		
		
		if ((isCoordinates == true) && (r.location != kCFNotFound)) {
			double sign = 1.;
			CFArrayRef dmsArray = CFStringCreateArrayBySeparatingStrings(kCFAllocatorDefault, headerValue, CFSTR(":"));

			if (CFArrayGetCount(dmsArray) == 3) {			
				if (CFStringGetDoubleValue(CFArrayGetValueAtIndex(dmsArray, 0)) < 0) {
					sign = -1;
				}
									
				// Work for either DMS or HMS, since the input format is kept. It is not a transformation, just a flattening.
				number = sign * ( fabs(CFStringGetDoubleValue(CFArrayGetValueAtIndex(dmsArray, 0))) +
									   CFStringGetDoubleValue(CFArrayGetValueAtIndex(dmsArray, 1)) / 60 +
									   CFStringGetDoubleValue(CFArrayGetValueAtIndex(dmsArray, 2)) / 3600 );

			}

			CFRelease(dmsArray);
		}
		else {
			number = CFStringGetDoubleValue(headerValue);			
		}

		CFNumberRef value = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &number);
		CFStringRef cfImporterAttrName = CFStringCreateWithCString(kCFAllocatorDefault, importerAttrName, kCFStringEncodingUTF8);
		CFDictionaryAddValue(attributes, cfImporterAttrName, value);
		CFRelease(value);
		CFRelease(cfImporterAttrName);

		CFRelease(headerValue);
	}
}
Пример #5
0
float NumericControl::getFloatValue( WindowRef window )
{
	CFStringRef stringRef = getStringRef( window );
	
	if( stringRef )
	{
		mValue = CFStringGetDoubleValue( stringRef );
		return mValue;
	}
	
	return 0.0f;
}
Пример #6
0
bool FLACMetadata::ReadMetadata(CFErrorRef *error)
{
	// Start from scratch
	CFDictionaryRemoveAllValues(mMetadata);
	CFDictionaryRemoveAllValues(mChangedMetadata);
	
	UInt8 buf [PATH_MAX];
	if(!CFURLGetFileSystemRepresentation(mURL, false, buf, PATH_MAX))
		return false;
	
	FLAC__Metadata_Chain *chain = FLAC__metadata_chain_new();

	// ENOMEM sux
	if(NULL == chain)
		return false;
	
	if(!FLAC__metadata_chain_read(chain, reinterpret_cast<const char *>(buf))) {

		// Attempt to provide a descriptive error message
		if(NULL != error) {
			CFMutableDictionaryRef errorDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 
																			   32,
																			   &kCFTypeDictionaryKeyCallBacks,
																			   &kCFTypeDictionaryValueCallBacks);
			
			switch(FLAC__metadata_chain_status(chain)) {
				case FLAC__METADATA_CHAIN_STATUS_NOT_A_FLAC_FILE:
				{
					CFStringRef displayName = CreateDisplayNameForURL(mURL);
					CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorDefault, 
																	   NULL, 
																	   CFCopyLocalizedString(CFSTR("The file “%@” is not a valid FLAC file."), ""), 
																	   displayName);
					
					CFDictionarySetValue(errorDictionary, 
										 kCFErrorLocalizedDescriptionKey, 
										 errorString);

					CFDictionarySetValue(errorDictionary, 
										 kCFErrorLocalizedFailureReasonKey, 
										 CFCopyLocalizedString(CFSTR("Not a FLAC file"), ""));
					
					CFDictionarySetValue(errorDictionary, 
										 kCFErrorLocalizedRecoverySuggestionKey, 
										 CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""));
					
					CFRelease(errorString), errorString = NULL;
					CFRelease(displayName), displayName = NULL;
					
					break;
				}
					
					
				case FLAC__METADATA_CHAIN_STATUS_BAD_METADATA:
				{
					CFStringRef displayName = CreateDisplayNameForURL(mURL);
					CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorDefault, 
																	   NULL, 
																	   CFCopyLocalizedString(CFSTR("The file “%@” is not a valid FLAC file."), ""), 
																	   displayName);
					
					CFDictionarySetValue(errorDictionary, 
										 kCFErrorLocalizedDescriptionKey, 
										 errorString);
					
					CFDictionarySetValue(errorDictionary, 
										 kCFErrorLocalizedFailureReasonKey, 
										 CFCopyLocalizedString(CFSTR("Not a FLAC file"), ""));
					
					CFDictionarySetValue(errorDictionary, 
										 kCFErrorLocalizedRecoverySuggestionKey, 
										 CFCopyLocalizedString(CFSTR("The file contains bad metadata."), ""));
					
					CFRelease(errorString), errorString = NULL;
					CFRelease(displayName), displayName = NULL;
					
					break;
				}
					
				default:
				{
					CFStringRef displayName = CreateDisplayNameForURL(mURL);
					CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorDefault, 
																	   NULL, 
																	   CFCopyLocalizedString(CFSTR("The file “%@” is not a valid FLAC file."), ""), 
																	   displayName);
					
					CFDictionarySetValue(errorDictionary, 
										 kCFErrorLocalizedDescriptionKey, 
										 errorString);
					
					CFDictionarySetValue(errorDictionary, 
										 kCFErrorLocalizedFailureReasonKey, 
										 CFCopyLocalizedString(CFSTR("Not a FLAC file"), ""));
					
					CFDictionarySetValue(errorDictionary, 
										 kCFErrorLocalizedRecoverySuggestionKey, 
										 CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""));
					
					CFRelease(errorString), errorString = NULL;
					CFRelease(displayName), displayName = NULL;
					
					break;
				}
			}
			
			*error = CFErrorCreate(kCFAllocatorDefault, 
								   AudioMetadataErrorDomain, 
								   AudioMetadataFileFormatNotRecognizedError, 
								   errorDictionary);
			
			CFRelease(errorDictionary), errorDictionary = NULL;
		}

		FLAC__metadata_chain_delete(chain), chain = NULL;
		
		return false;
	}
	
	FLAC__Metadata_Iterator *iterator = FLAC__metadata_iterator_new();

	if(NULL == iterator) {
		FLAC__metadata_chain_delete(chain), chain = NULL;		
		return false;
	}
	
	CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, CFSTR("FLAC"));

	FLAC__metadata_iterator_init(iterator, chain);
	
	FLAC__StreamMetadata *block = NULL;
	
	CFMutableDictionaryRef additionalMetadata = CFDictionaryCreateMutable(kCFAllocatorDefault, 
																		  32,
																		  &kCFTypeDictionaryKeyCallBacks,
																		  &kCFTypeDictionaryValueCallBacks);
	
	do {
		block = FLAC__metadata_iterator_get_block(iterator);
		
		if(NULL == block)
			break;
		
		switch(block->type) {					
			case FLAC__METADATA_TYPE_VORBIS_COMMENT:				
				for(unsigned i = 0; i < block->data.vorbis_comment.num_comments; ++i) {
					
					char *fieldName = NULL;
					char *fieldValue = NULL;
					
					// Let FLAC parse the comment for us
					if(!FLAC__metadata_object_vorbiscomment_entry_to_name_value_pair(block->data.vorbis_comment.comments[i], &fieldName, &fieldValue)) {
						// Ignore malformed comments
						continue;
					}
					
					CFStringRef key = CFStringCreateWithBytesNoCopy(kCFAllocatorDefault,
																	reinterpret_cast<const UInt8 *>(fieldName),
																	strlen(fieldName), 
																	kCFStringEncodingASCII,
																	false,
																	kCFAllocatorMalloc);

					CFStringRef value = CFStringCreateWithBytesNoCopy(kCFAllocatorDefault,
																	  reinterpret_cast<const UInt8 *>(fieldValue),
																	  strlen(fieldValue), 
																	  kCFStringEncodingUTF8,
																	  false,
																	  kCFAllocatorMalloc);
					
					if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ALBUM"), kCFCompareCaseInsensitive))
						CFDictionarySetValue(mMetadata, kMetadataAlbumTitleKey, value);
					else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ARTIST"), kCFCompareCaseInsensitive))
						CFDictionarySetValue(mMetadata, kMetadataArtistKey, value);
					else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ALBUMARTIST"), kCFCompareCaseInsensitive))
						CFDictionarySetValue(mMetadata, kMetadataAlbumArtistKey, value);
					else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("COMPOSER"), kCFCompareCaseInsensitive))
						CFDictionarySetValue(mMetadata, kMetadataComposerKey, value);
					else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("GENRE"), kCFCompareCaseInsensitive))
						CFDictionarySetValue(mMetadata, kMetadataGenreKey, value);
					else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("DATE"), kCFCompareCaseInsensitive))
						CFDictionarySetValue(mMetadata, kMetadataReleaseDateKey, value);
					else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("DESCRIPTION"), kCFCompareCaseInsensitive))
						CFDictionarySetValue(mMetadata, kMetadataCommentKey, value);
					else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("TITLE"), kCFCompareCaseInsensitive))
						CFDictionarySetValue(mMetadata, kMetadataTitleKey, value);
					else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("TRACKNUMBER"), kCFCompareCaseInsensitive)) {
						int num = CFStringGetIntValue(value);
						CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &num);
						CFDictionarySetValue(mMetadata, kMetadataTrackNumberKey, number);
						CFRelease(number), number = NULL;
					}
					else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("TRACKTOTAL"), kCFCompareCaseInsensitive)) {
						int num = CFStringGetIntValue(value);
						CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &num);
						CFDictionarySetValue(mMetadata, kMetadataTrackTotalKey, number);
						CFRelease(number), number = NULL;
					}
					else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("COMPILATION"), kCFCompareCaseInsensitive))
						CFDictionarySetValue(mMetadata, kMetadataCompilationKey, CFStringGetIntValue(value) ? kCFBooleanTrue : kCFBooleanFalse);
					else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("DISCNUMBER"), kCFCompareCaseInsensitive)) {
						int num = CFStringGetIntValue(value);
						CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &num);
						CFDictionarySetValue(mMetadata, kMetadataDiscNumberKey, number);
						CFRelease(number), number = NULL;
					}
					else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("DISCTOTAL"), kCFCompareCaseInsensitive)) {
						int num = CFStringGetIntValue(value);
						CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &num);
						CFDictionarySetValue(mMetadata, kMetadataDiscTotalKey, number);
						CFRelease(number), number = NULL;
					}
					else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ISRC"), kCFCompareCaseInsensitive))
						CFDictionarySetValue(mMetadata, kMetadataISRCKey, value);
					else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("MCN"), kCFCompareCaseInsensitive))
						CFDictionarySetValue(mMetadata, kMetadataMCNKey, value);
					else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("REPLAYGAIN_REFERENCE_LOUDNESS"), kCFCompareCaseInsensitive)) {
						double num = CFStringGetDoubleValue(value);
						CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &num);
						CFDictionarySetValue(mMetadata, kReplayGainReferenceLoudnessKey, number);
						CFRelease(number), number = NULL;
					}
					else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("REPLAYGAIN_TRACK_GAIN"), kCFCompareCaseInsensitive)) {
						double num = CFStringGetDoubleValue(value);
						CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &num);
						CFDictionarySetValue(mMetadata, kReplayGainTrackGainKey, number);
						CFRelease(number), number = NULL;
					}
					else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("REPLAYGAIN_TRACK_PEAK"), kCFCompareCaseInsensitive)) {
						double num = CFStringGetDoubleValue(value);
						CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &num);
						CFDictionarySetValue(mMetadata, kReplayGainTrackPeakKey, number);
						CFRelease(number), number = NULL;
					}
					else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("REPLAYGAIN_ALBUM_GAIN"), kCFCompareCaseInsensitive)) {
						double num = CFStringGetDoubleValue(value);
						CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &num);
						CFDictionarySetValue(mMetadata, kReplayGainAlbumGainKey, number);
						CFRelease(number), number = NULL;
					}
					else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("REPLAYGAIN_ALBUM_PEAK"), kCFCompareCaseInsensitive)) {
						double num = CFStringGetDoubleValue(value);
						CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &num);
						CFDictionarySetValue(mMetadata, kReplayGainAlbumPeakKey, number);
						CFRelease(number), number = NULL;
					}
					// Put all unknown tags into the additional metadata
					else
						CFDictionarySetValue(additionalMetadata, key, value);
					
					CFRelease(key), key = NULL;
					CFRelease(value), value = NULL;
					
					fieldName = NULL;
					fieldValue = NULL;
				}
				break;
				
			case FLAC__METADATA_TYPE_PICTURE:
			{
				CFDataRef data = CFDataCreate(kCFAllocatorDefault, block->data.picture.data, block->data.picture.data_length);
				CFDictionarySetValue(mMetadata, kAlbumArtFrontCoverKey, data);
				CFRelease(data), data = NULL;
			}
			break;
				
			case FLAC__METADATA_TYPE_STREAMINFO:
			{
				CFNumberRef sampleRate = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &block->data.stream_info.sample_rate);
				CFDictionarySetValue(mMetadata, kPropertiesSampleRateKey, sampleRate);
				CFRelease(sampleRate), sampleRate = NULL;

				CFNumberRef channelsPerFrame = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &block->data.stream_info.channels);
				CFDictionarySetValue(mMetadata, kPropertiesChannelsPerFrameKey, channelsPerFrame);
				CFRelease(channelsPerFrame), channelsPerFrame = NULL;

				CFNumberRef bitsPerChannel = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &block->data.stream_info.bits_per_sample);
				CFDictionarySetValue(mMetadata, kPropertiesBitsPerChannelKey, bitsPerChannel);
				CFRelease(bitsPerChannel), bitsPerChannel = NULL;
				
				CFNumberRef totalFrames = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongLongType, &block->data.stream_info.total_samples);
				CFDictionarySetValue(mMetadata, kPropertiesTotalFramesKey, totalFrames);
				CFRelease(totalFrames), totalFrames = NULL;

				double length = static_cast<double>(block->data.stream_info.total_samples / block->data.stream_info.sample_rate);
				CFNumberRef duration = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &length);
				CFDictionarySetValue(mMetadata, kPropertiesDurationKey, duration);
				CFRelease(duration), duration = NULL;

				double losslessBitrate = static_cast<double>(block->data.stream_info.sample_rate * block->data.stream_info.channels * block->data.stream_info.bits_per_sample) / 1000;
				CFNumberRef bitrate = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &losslessBitrate);
				CFDictionarySetValue(mMetadata, kPropertiesBitrateKey, bitrate);
				CFRelease(bitrate), bitrate = NULL;
			}
			break;

			case FLAC__METADATA_TYPE_PADDING:						break;
			case FLAC__METADATA_TYPE_APPLICATION:					break;
			case FLAC__METADATA_TYPE_SEEKTABLE:						break;
			case FLAC__METADATA_TYPE_CUESHEET:						break;
			case FLAC__METADATA_TYPE_UNDEFINED:						break;

			default:												break;
		}
	} while(FLAC__metadata_iterator_next(iterator));

	if(CFDictionaryGetCount(additionalMetadata))
		SetAdditionalMetadata(additionalMetadata);
	
	CFRelease(additionalMetadata), additionalMetadata = NULL;
	
	FLAC__metadata_iterator_delete(iterator), iterator = NULL;
	FLAC__metadata_chain_delete(chain), chain = NULL;
	
	return true;
}
bool SFB::Audio::AddID3v2TagToDictionary(CFMutableDictionaryRef dictionary, std::vector<std::shared_ptr<AttachedPicture>>& attachedPictures, const TagLib::ID3v2::Tag *tag)
{
	if(nullptr == dictionary || nullptr == tag)
		return false;

	// Add the basic tags not specific to ID3v2
	AddTagToDictionary(dictionary, tag);

	// Release date
	auto frameList = tag->frameListMap()["TDRC"];
	if(!frameList.isEmpty()) {
		/*
		 The timestamp fields are based on a subset of ISO 8601. When being as
		 precise as possible the format of a time string is
		 yyyy-MM-ddTHH:mm:ss (year, "-", month, "-", day, "T", hour (out of
		 24), ":", minutes, ":", seconds), but the precision may be reduced by
		 removing as many time indicators as wanted. Hence valid timestamps
		 are
		 yyyy, yyyy-MM, yyyy-MM-dd, yyyy-MM-ddTHH, yyyy-MM-ddTHH:mm and
		 yyyy-MM-ddTHH:mm:ss. All time stamps are UTC. For durations, use
		 the slash character as described in 8601, and for multiple non-
		 contiguous dates, use multiple strings, if allowed by the frame
		 definition.
		 */

		TagLib::AddStringToCFDictionary(dictionary, Metadata::kReleaseDateKey, frameList.front()->toString());
	}

	// Extract composer if present
	frameList = tag->frameListMap()["TCOM"];
	if(!frameList.isEmpty())
		TagLib::AddStringToCFDictionary(dictionary, Metadata::kComposerKey, frameList.front()->toString());

	// Extract album artist
	frameList = tag->frameListMap()["TPE2"];
	if(!frameList.isEmpty())
		TagLib::AddStringToCFDictionary(dictionary, Metadata::kAlbumArtistKey, frameList.front()->toString());

	// BPM
	frameList = tag->frameListMap()["TBPM"];
	if(!frameList.isEmpty()) {
		bool ok = false;
		int BPM = frameList.front()->toString().toInt(&ok);
		if(ok)
			AddIntToDictionary(dictionary, Metadata::kBPMKey, BPM);
	}

	// Rating
	TagLib::ID3v2::PopularimeterFrame *popularimeter = nullptr;
	frameList = tag->frameListMap()["POPM"];
	if(!frameList.isEmpty() && nullptr != (popularimeter = dynamic_cast<TagLib::ID3v2::PopularimeterFrame *>(frameList.front())))
		AddIntToDictionary(dictionary, Metadata::kRatingKey, popularimeter->rating());

	// Extract total tracks if present
	frameList = tag->frameListMap()["TRCK"];
	if(!frameList.isEmpty()) {
		// Split the tracks at '/'
		TagLib::String s = frameList.front()->toString();

		bool ok;
		size_t pos = s.find("/", 0);
		if(TagLib::String::npos() != pos) {
			int trackNum = s.substr(0, pos).toInt(&ok);
			if(ok)
				AddIntToDictionary(dictionary, Metadata::kTrackNumberKey, trackNum);

			int trackTotal = s.substr(pos + 1).toInt(&ok);
			if(ok)
				AddIntToDictionary(dictionary, Metadata::kTrackTotalKey, trackTotal);
		}
		else if(s.length()) {
			int trackNum = s.toInt(&ok);
			if(ok)
				AddIntToDictionary(dictionary, Metadata::kTrackNumberKey, trackNum);
		}
	}

	// Extract disc number and total discs
	frameList = tag->frameListMap()["TPOS"];
	if(!frameList.isEmpty()) {
		// Split the tracks at '/'
		TagLib::String s = frameList.front()->toString();

		bool ok;
		size_t pos = s.find("/", 0);
		if(TagLib::String::npos() != pos) {
			int discNum = s.substr(0, pos).toInt(&ok);
			if(ok)
				AddIntToDictionary(dictionary, Metadata::kDiscNumberKey, discNum);

			int discTotal = s.substr(pos + 1).toInt(&ok);
			if(ok)
				AddIntToDictionary(dictionary, Metadata::kDiscTotalKey, discTotal);
		}
		else if(s.length()) {
			int discNum = s.toInt(&ok);
			if(ok)
				AddIntToDictionary(dictionary, Metadata::kDiscNumberKey, discNum);
		}
	}

	// Lyrics
	frameList = tag->frameListMap()["USLT"];
	if(!frameList.isEmpty())
		TagLib::AddStringToCFDictionary(dictionary, Metadata::kLyricsKey, frameList.front()->toString());

	// Extract compilation if present (iTunes TCMP tag)
	frameList = tag->frameListMap()["TCMP"];
	if(!frameList.isEmpty())
		// It seems that the presence of this frame indicates a compilation
		CFDictionarySetValue(dictionary, Metadata::kCompilationKey, kCFBooleanTrue);

	frameList = tag->frameListMap()["TSRC"];
	if(!frameList.isEmpty())
		TagLib::AddStringToCFDictionary(dictionary, Metadata::kISRCKey, frameList.front()->toString());

	// MusicBrainz
	auto musicBrainzReleaseIDFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(const_cast<TagLib::ID3v2::Tag *>(tag), "MusicBrainz Album Id");
	if(musicBrainzReleaseIDFrame)
		TagLib::AddStringToCFDictionary(dictionary, Metadata::kMusicBrainzReleaseIDKey, musicBrainzReleaseIDFrame->fieldList().back());

	auto musicBrainzRecordingIDFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(const_cast<TagLib::ID3v2::Tag *>(tag), "MusicBrainz Track Id");
	if(musicBrainzRecordingIDFrame)
		TagLib::AddStringToCFDictionary(dictionary, Metadata::kMusicBrainzRecordingIDKey, musicBrainzRecordingIDFrame->fieldList().back());

	// Sorting and grouping
	frameList = tag->frameListMap()["TSOT"];
	if(!frameList.isEmpty())
		TagLib::AddStringToCFDictionary(dictionary, Metadata::kTitleSortOrderKey, frameList.front()->toString());

	frameList = tag->frameListMap()["TSOA"];
	if(!frameList.isEmpty())
		TagLib::AddStringToCFDictionary(dictionary, Metadata::kAlbumTitleSortOrderKey, frameList.front()->toString());

	frameList = tag->frameListMap()["TSOP"];
	if(!frameList.isEmpty())
		TagLib::AddStringToCFDictionary(dictionary, Metadata::kArtistSortOrderKey, frameList.front()->toString());

	frameList = tag->frameListMap()["TSO2"];
	if(!frameList.isEmpty())
		TagLib::AddStringToCFDictionary(dictionary, Metadata::kAlbumArtistSortOrderKey, frameList.front()->toString());

	frameList = tag->frameListMap()["TSOC"];
	if(!frameList.isEmpty())
		TagLib::AddStringToCFDictionary(dictionary, Metadata::kComposerSortOrderKey, frameList.front()->toString());

	frameList = tag->frameListMap()["TIT1"];
	if(!frameList.isEmpty())
		TagLib::AddStringToCFDictionary(dictionary, Metadata::kGroupingKey, frameList.front()->toString());

	// ReplayGain
	bool foundReplayGain = false;

	// Preference is TXXX frames, RVA2 frame, then LAME header
	auto trackGainFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(const_cast<TagLib::ID3v2::Tag *>(tag), "REPLAYGAIN_TRACK_GAIN");
	auto trackPeakFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(const_cast<TagLib::ID3v2::Tag *>(tag), "REPLAYGAIN_TRACK_PEAK");
	auto albumGainFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(const_cast<TagLib::ID3v2::Tag *>(tag), "REPLAYGAIN_ALBUM_GAIN");
	auto albumPeakFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(const_cast<TagLib::ID3v2::Tag *>(tag), "REPLAYGAIN_ALBUM_PEAK");

	if(!trackGainFrame)
		trackGainFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(const_cast<TagLib::ID3v2::Tag *>(tag), "replaygain_track_gain");
	if(trackGainFrame) {
		SFB::CFString str(trackGainFrame->fieldList().back().toCString(true), kCFStringEncodingUTF8);
		double num = CFStringGetDoubleValue(str);

		AddDoubleToDictionary(dictionary, Metadata::kTrackGainKey, num);
		AddDoubleToDictionary(dictionary, Metadata::kReferenceLoudnessKey, 89.0);

		foundReplayGain = true;
	}

	if(!trackPeakFrame)
		trackPeakFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(const_cast<TagLib::ID3v2::Tag *>(tag), "replaygain_track_peak");
	if(trackPeakFrame) {
		SFB::CFString str(trackPeakFrame->fieldList().back().toCString(true), kCFStringEncodingUTF8);
		double num = CFStringGetDoubleValue(str);

		AddDoubleToDictionary(dictionary, Metadata::kTrackPeakKey, num);
	}

	if(!albumGainFrame)
		albumGainFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(const_cast<TagLib::ID3v2::Tag *>(tag), "replaygain_album_gain");
	if(albumGainFrame) {
		SFB::CFString str(albumGainFrame->fieldList().back().toCString(true), kCFStringEncodingUTF8);
		double num = CFStringGetDoubleValue(str);

		AddDoubleToDictionary(dictionary, Metadata::kAlbumGainKey, num);
		AddDoubleToDictionary(dictionary, Metadata::kReferenceLoudnessKey, 89.0);

		foundReplayGain = true;
	}

	if(!albumPeakFrame)
		albumPeakFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(const_cast<TagLib::ID3v2::Tag *>(tag), "replaygain_album_peak");
	if(albumPeakFrame) {
		SFB::CFString str(albumPeakFrame->fieldList().back().toCString(true), kCFStringEncodingUTF8);
		double num = CFStringGetDoubleValue(str);

		AddDoubleToDictionary(dictionary, Metadata::kAlbumPeakKey, num);
	}

	// If nothing found check for RVA2 frame
	if(!foundReplayGain) {
		frameList = tag->frameListMap()["RVA2"];

		for(auto frameIterator : tag->frameListMap()["RVA2"]) {
			TagLib::ID3v2::RelativeVolumeFrame *relativeVolume = dynamic_cast<TagLib::ID3v2::RelativeVolumeFrame *>(frameIterator);
			if(!relativeVolume)
				continue;

			// Attempt to use the master volume if present
			auto channels		= relativeVolume->channels();
			auto channelType	= TagLib::ID3v2::RelativeVolumeFrame::MasterVolume;

			// Fall back on whatever else exists in the frame
			if(!channels.contains(TagLib::ID3v2::RelativeVolumeFrame::MasterVolume))
				channelType = channels.front();

			float volumeAdjustment = relativeVolume->volumeAdjustment(channelType);

			if(TagLib::String("track", TagLib::String::Latin1) == relativeVolume->identification()) {
				if((int)volumeAdjustment)
					AddFloatToDictionary(dictionary, Metadata::kTrackGainKey, volumeAdjustment);
			}
			else if(TagLib::String("album", TagLib::String::Latin1) == relativeVolume->identification()) {
				if((int)volumeAdjustment)
					AddFloatToDictionary(dictionary, Metadata::kAlbumGainKey, volumeAdjustment);
			}
			// Fall back to track gain if identification is not specified
			else {
				if((int)volumeAdjustment)
					AddFloatToDictionary(dictionary, Metadata::kTrackGainKey, volumeAdjustment);
			}
		}
	}

	// Extract album art if present
	for(auto it : tag->frameListMap()["APIC"]) {
		TagLib::ID3v2::AttachedPictureFrame *frame = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame *>(it);
		if(frame) {
			SFB::CFData data((const UInt8 *)frame->picture().data(), (CFIndex)frame->picture().size());

			SFB::CFString description;
			if(!frame->description().isEmpty())
				description = CFString(frame->description().toCString(true), kCFStringEncodingUTF8);

			attachedPictures.push_back(std::make_shared<AttachedPicture>(data, (AttachedPicture::Type)frame->type(), description));
		}
	}

	return true;
}
bool SFB::Audio::AddAPETagToDictionary(CFMutableDictionaryRef dictionary, std::vector<std::shared_ptr<AttachedPicture>>& attachedPictures, const TagLib::APE::Tag *tag)
{
	if(nullptr == dictionary || nullptr == tag)
		return false;

	if(tag->isEmpty())
		return true;

	SFB::CFMutableDictionary additionalMetadata(0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
	
	for(auto iterator : tag->itemListMap()) {
		auto item = iterator.second;

		if(item.isEmpty())
			continue;

		if(TagLib::APE::Item::Text == item.type()) {
			SFB::CFString key(item.key().toCString(true), kCFStringEncodingUTF8);
			SFB::CFString value(item.toString().toCString(true), kCFStringEncodingUTF8);

			if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ALBUM"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kAlbumTitleKey, value);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ARTIST"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kArtistKey, value);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ALBUMARTIST"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kAlbumArtistKey, value);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("COMPOSER"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kComposerKey, value);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("GENRE"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kGenreKey, value);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("DATE"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kReleaseDateKey, value);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("DESCRIPTION"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kCommentKey, value);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("TITLE"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kTitleKey, value);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("TRACKNUMBER"), kCFCompareCaseInsensitive))
				AddIntToDictionary(dictionary, Metadata::kTrackNumberKey, CFStringGetIntValue(value));
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("TRACKTOTAL"), kCFCompareCaseInsensitive))
				AddIntToDictionary(dictionary, Metadata::kTrackTotalKey, CFStringGetIntValue(value));
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("COMPILATION"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kCompilationKey, CFStringGetIntValue(value) ? kCFBooleanTrue : kCFBooleanFalse);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("DISCNUMBER"), kCFCompareCaseInsensitive))
				AddIntToDictionary(dictionary, Metadata::kDiscNumberKey, CFStringGetIntValue(value));
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("DISCTOTAL"), kCFCompareCaseInsensitive))
				AddIntToDictionary(dictionary, Metadata::kDiscTotalKey, CFStringGetIntValue(value));
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("LYRICS"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kLyricsKey, value);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("BPM"), kCFCompareCaseInsensitive))
				AddIntToDictionary(dictionary, Metadata::kBPMKey, CFStringGetIntValue(value));
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("RATING"), kCFCompareCaseInsensitive))
				AddIntToDictionary(dictionary, Metadata::kRatingKey, CFStringGetIntValue(value));
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ISRC"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kISRCKey, value);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("MCN"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kMCNKey, value);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("MUSICBRAINZ_ALBUMID"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kMusicBrainzReleaseIDKey, value);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("MUSICBRAINZ_TRACKID"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kMusicBrainzRecordingIDKey, value);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("TITLESORT"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kTitleSortOrderKey, value);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ALBUMTITLESORT"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kAlbumTitleSortOrderKey, value);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ARTISTSORT"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kArtistSortOrderKey, value);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ALBUMARTISTSORT"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kAlbumArtistSortOrderKey, value);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("COMPOSERSORT"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kComposerSortOrderKey, value);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("GROUPING"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kGroupingKey, value);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("REPLAYGAIN_REFERENCE_LOUDNESS"), kCFCompareCaseInsensitive))
				AddDoubleToDictionary(dictionary, Metadata::kReferenceLoudnessKey, CFStringGetDoubleValue(value));
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("REPLAYGAIN_TRACK_GAIN"), kCFCompareCaseInsensitive))
				AddDoubleToDictionary(dictionary, Metadata::kTrackGainKey, CFStringGetDoubleValue(value));
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("REPLAYGAIN_TRACK_PEAK"), kCFCompareCaseInsensitive))
				AddDoubleToDictionary(dictionary, Metadata::kTrackPeakKey, CFStringGetDoubleValue(value));
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("REPLAYGAIN_ALBUM_GAIN"), kCFCompareCaseInsensitive))
				AddDoubleToDictionary(dictionary, Metadata::kAlbumGainKey, CFStringGetDoubleValue(value));
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("REPLAYGAIN_ALBUM_PEAK"), kCFCompareCaseInsensitive))
				AddDoubleToDictionary(dictionary, Metadata::kAlbumPeakKey, CFStringGetDoubleValue(value));
#if 0
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("METADATA_BLOCK_PICTURE"), kCFCompareCaseInsensitive)) {
				// Handle embedded pictures
				for(auto blockIterator : item.values()) {
					auto encodedBlock = blockIterator.data(TagLib::String::UTF8);

					// Decode the Base-64 encoded data
					auto decodedBlock = TagLib::DecodeBase64(encodedBlock);

					// Create the picture
					TagLib::FLAC::Picture picture;
					picture.parse(decodedBlock);

					SFB::CFData data((const UInt8 *)picture.data().data(), picture.data().size());

					SFB::CFString description = nullptr;
					if(!picture.description().isNull())
						description(picture.description().toCString(true), kCFStringEncodingUTF8);

					attachedPictures.push_back(std::make_shared<AttachedPicture>(data, (AttachedPicture::Type)picture.type(), description));
				}
			}
#endif
			// Put all unknown tags into the additional metadata
			else
				CFDictionarySetValue(additionalMetadata, key, value);
		}
		else if(TagLib::APE::Item::Binary == item.type()) {
			SFB::CFString key(item.key().toCString(true), kCFStringEncodingUTF8);

			// From http://www.hydrogenaudio.org/forums/index.php?showtopic=40603&view=findpost&p=504669
			/*
			 <length> 32 bit
			 <flags with binary bit set> 32 bit
			 <field name> "Cover Art (Front)"|"Cover Art (Back)"
			 0x00
			 <description> UTF-8 string (needs to be a file name to be recognized by AudioShell - meh)
			 0x00
			 <cover data> binary
			 */
			if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("Cover Art (Front)"), kCFCompareCaseInsensitive) || kCFCompareEqualTo == CFStringCompare(key, CFSTR("Cover Art (Back)"), kCFCompareCaseInsensitive)) {
				auto binaryData = item.binaryData();
				size_t pos = binaryData.find('\0');
				if(TagLib::ByteVector::npos() != pos && 3 < binaryData.size()) {
					SFB::CFData data((const UInt8 *)binaryData.mid(pos + 1).data(), (CFIndex)(binaryData.size() - pos - 1));
					SFB::CFString description(TagLib::String(binaryData.mid(0, pos), TagLib::String::UTF8).toCString(true), kCFStringEncodingUTF8);

					attachedPictures.push_back(std::make_shared<AttachedPicture>(data, kCFCompareEqualTo == CFStringCompare(key, CFSTR("Cover Art (Front)"), kCFCompareCaseInsensitive) ? AttachedPicture::Type::FrontCover : AttachedPicture::Type::BackCover, description));
				}
			}
		}
	}

	if(CFDictionaryGetCount(additionalMetadata))
		CFDictionarySetValue(dictionary, Metadata::kAdditionalMetadataKey, additionalMetadata);

	return true;
}
bool SFB::Audio::AddXiphCommentToDictionary(CFMutableDictionaryRef dictionary, std::vector<std::shared_ptr<AttachedPicture>>& attachedPictures, const TagLib::Ogg::XiphComment *tag)
{
	if(nullptr == dictionary || nullptr == tag)
		return false;

	SFB::CFMutableDictionary additionalMetadata = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);

	for(auto it : tag->fieldListMap()) {
		// According to the Xiph comment specification keys should only contain a limited subset of ASCII, but UTF-8 is a safer choice
		SFB::CFString key = CFStringCreateWithCString(kCFAllocatorDefault, it.first.toCString(true), kCFStringEncodingUTF8);
		
		// Vorbis allows multiple comments with the same key, but this isn't supported by AudioMetadata
		SFB::CFString value = CFStringCreateWithCString(kCFAllocatorDefault, it.second.front().toCString(true), kCFStringEncodingUTF8);
		
		if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ALBUM"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kAlbumTitleKey, value);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ARTIST"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kArtistKey, value);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ALBUMARTIST"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kAlbumArtistKey, value);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("COMPOSER"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kComposerKey, value);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("GENRE"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kGenreKey, value);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("DATE"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kReleaseDateKey, value);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("DESCRIPTION"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kCommentKey, value);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("TITLE"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kTitleKey, value);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("TRACKNUMBER"), kCFCompareCaseInsensitive))
			AddIntToDictionary(dictionary, Metadata::kTrackNumberKey, CFStringGetIntValue(value));
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("TRACKTOTAL"), kCFCompareCaseInsensitive))
			AddIntToDictionary(dictionary, Metadata::kTrackTotalKey, CFStringGetIntValue(value));
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("COMPILATION"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kCompilationKey, CFStringGetIntValue(value) ? kCFBooleanTrue : kCFBooleanFalse);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("DISCNUMBER"), kCFCompareCaseInsensitive))
			AddIntToDictionary(dictionary, Metadata::kDiscNumberKey, CFStringGetIntValue(value));
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("DISCTOTAL"), kCFCompareCaseInsensitive))
			AddIntToDictionary(dictionary, Metadata::kDiscTotalKey, CFStringGetIntValue(value));
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("LYRICS"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kLyricsKey, value);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("BPM"), kCFCompareCaseInsensitive))
			AddIntToDictionary(dictionary, Metadata::kBPMKey, CFStringGetIntValue(value));
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("RATING"), kCFCompareCaseInsensitive))
			AddIntToDictionary(dictionary, Metadata::kRatingKey, CFStringGetIntValue(value));
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ISRC"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kISRCKey, value);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("MCN"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kMCNKey, value);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("MUSICBRAINZ_ALBUMID"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kMusicBrainzReleaseIDKey, value);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("MUSICBRAINZ_TRACKID"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kMusicBrainzRecordingIDKey, value);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("TITLESORT"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kTitleSortOrderKey, value);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ALBUMTITLESORT"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kAlbumTitleSortOrderKey, value);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ARTISTSORT"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kArtistSortOrderKey, value);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ALBUMARTISTSORT"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kAlbumArtistSortOrderKey, value);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("COMPOSERSORT"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kComposerSortOrderKey, value);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("GROUPING"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kGroupingKey, value);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("REPLAYGAIN_REFERENCE_LOUDNESS"), kCFCompareCaseInsensitive))
			AddDoubleToDictionary(dictionary, Metadata::kReferenceLoudnessKey, CFStringGetDoubleValue(value));
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("REPLAYGAIN_TRACK_GAIN"), kCFCompareCaseInsensitive))
			AddDoubleToDictionary(dictionary, Metadata::kTrackGainKey, CFStringGetDoubleValue(value));
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("REPLAYGAIN_TRACK_PEAK"), kCFCompareCaseInsensitive))
			AddDoubleToDictionary(dictionary, Metadata::kTrackPeakKey, CFStringGetDoubleValue(value));
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("REPLAYGAIN_ALBUM_GAIN"), kCFCompareCaseInsensitive))
			AddDoubleToDictionary(dictionary, Metadata::kAlbumGainKey, CFStringGetDoubleValue(value));
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("REPLAYGAIN_ALBUM_PEAK"), kCFCompareCaseInsensitive))
			AddDoubleToDictionary(dictionary, Metadata::kAlbumPeakKey, CFStringGetDoubleValue(value));
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("METADATA_BLOCK_PICTURE"), kCFCompareCaseInsensitive)) {
			// Handle embedded pictures
			for(auto blockIterator : it.second) {
				auto encodedBlock = blockIterator.data(TagLib::String::UTF8);

				// Decode the Base-64 encoded data
				auto decodedBlock = TagLib::DecodeBase64(encodedBlock);

				// Create the picture
				TagLib::FLAC::Picture picture;
				picture.parse(decodedBlock);
				
				SFB::CFData data = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)picture.data().data(), (CFIndex)picture.data().size());

				SFB::CFString description;
				if(!picture.description().isEmpty())
					description = CFStringCreateWithCString(kCFAllocatorDefault, picture.description().toCString(true), kCFStringEncodingUTF8);

				attachedPictures.push_back(std::make_shared<AttachedPicture>(data, (AttachedPicture::Type)picture.type(), description));
			}
		}
		// Put all unknown tags into the additional metadata
		else
			CFDictionarySetValue(additionalMetadata, key, value);
	}
	
	if(CFDictionaryGetCount(additionalMetadata))
		CFDictionarySetValue(dictionary, Metadata::kAdditionalMetadataKey, additionalMetadata);

	return true;
}
Пример #10
0
int main(int argc, char** argv) {
  if(argc < 4) {
    PrintUsage(stdout);
    return 0;
  }
  
  /* <argv parsing> */
  
  /* Parse argument: mountpoint (CFString) */
  /*CFStringRef mountpoint = CFStringCreateWithCString(kCFAllocatorDefault, 
    argv[1], kCFStringEncodingUTF8); */
  /* Parse argument: mountpointRaw (char*) */
  char *mountpointRaw = argv[1];
  
  /* Parse argument: timeout (double) */
  double timeout;
  {
    CFStringRef timeoutString = CFStringCreateWithCString(kCFAllocatorDefault, 
							 argv[2], kCFStringEncodingUTF8);
    timeout = CFStringGetDoubleValue(timeoutString);
    CFRelease(timeoutString);
    if(timeout == 0.0) {
      fprintf(stdout, "Invalid argument: timeout (\"%s\"", argv[2]);
      return -1;
    }
  }
  
  /* Parse argument: mount_command */
  const char *mount_command = argv[3];
  
  /* </argv parsing> */
 
  CFNotificationCenterRef centerRef;
  CFStringRef notificationObjectName = CFSTR(FUSE_LISTEN_OBJECT);
  CFStringRef notificationName = CFSTR(FUSE_MOUNT_NOTIFICATION_NAME);
  CFRunLoopRef crl;
  
  if(DEBUGMODE) {
    DEBUG("Testing NotificationCallback...\n");
    NotificationCallback(NULL, NULL, notificationObjectName, NULL, NULL);
    DEBUG("Test completed. Adding observer...\n");
  }
  
  centerRef = CFNotificationCenterGetDistributedCenter();
  crl = CFRunLoopGetCurrent();
  
  /* Think. Will the child process also be an observer? I don't think so... */
  CFNotificationCenterAddObserver(centerRef, NULL, NotificationCallback, notificationName,
				  notificationObjectName, CFNotificationSuspensionBehaviorDrop);
    

  int forkRetval = fork();
  if(forkRetval == -1) {
    fprintf(stderr, "Could not fork!\n");
    return -1;
  }
  else if(forkRetval != 0) {
    // Parent process
    int childProcessPID = forkRetval;

    int waitpid_status = 0;
    DEBUG("Waiting for PID %d...\n", childProcessPID);
    int waitpidres = waitpid(childProcessPID, &waitpid_status, 0);
    if(waitpidres == childProcessPID) {
      if(!WIFEXITED(waitpid_status)) {
	DEBUG("Child process did not exit cleanly! Returning -1.");
	return -1;
      }
      
      int retval = WEXITSTATUS(waitpid_status);
      DEBUG("PID %d returned with exit code: %d Exiting fuse_wait with this exit code...\n", childProcessPID, retval);
      if(retval != 0) {
	DEBUG("Exit value indicates an error while executing mount command. Returning without\n");
	DEBUG("waiting for notification.\n");
	DEBUG("Returning retval: %d\n", retval);
	return retval;
      }
    }
    else {
      DEBUG("Abnormal termination of process %d. :( waitpid returned: %d\n", childProcessPID, waitpidres);
      return -1;
    }
    
    DEBUG("Running run loop a long time...\n");
    CFStringRef mountPathSnapshot = NULL;
    while(mountPathSnapshot == NULL) {
      int crlrimRetval = CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, true);
      DEBUG("Exited from run loop. Let's find out why... crlrimRetval: %d (handled: %d)\n", crlrimRetval, kCFRunLoopRunHandledSource);
      mountPathSnapshot = mountPath; // Might have been modified during run loop.
      if(crlrimRetval != kCFRunLoopRunHandledSource) {
	fprintf(stderr, "Did not receive a signal within %f seconds. Exiting...\n", timeout);
	break;
      }
      else if(mountPathSnapshot != NULL) {
	DEBUG("mountPathSnapshot: %X\n", (int)mountPathSnapshot);
	
	int mountPathUTF8Length = CFStringGetLength(mountPath) + 1;  // null terminator
	char *mountPathUTF8 = malloc(mountPathUTF8Length);
	memset(mountPathUTF8, 0, mountPathUTF8Length);
	GetCorruptedMacFUSEStringAsUTF8(mountPath, mountPathUTF8, mountPathUTF8Length);
	
	char *canonicalMountPath = malloc(PATH_MAX);
	char *canonicalMountpoint = malloc(PATH_MAX);
	memset(canonicalMountPath, 0, PATH_MAX);
	memset(canonicalMountpoint, 0, PATH_MAX);
	
	realpath(mountPathUTF8, canonicalMountPath);
	realpath(mountpointRaw, canonicalMountpoint);
	
	int cmpres = strncmp(canonicalMountPath, canonicalMountpoint, PATH_MAX);
	
	if(cmpres != 0) {
	  if(DEBUGMODE) {
	    DEBUG("Strings NOT equal. cmpres=%d\n", cmpres);
	    DEBUG("mountPath (UTF-8): \"%s\"\n", canonicalMountPath);
	    DEBUG("mountpoint (UTF-8): \"%s\"\n", canonicalMountpoint);
	  }
	  mountPathSnapshot = NULL;
	}
	else
	  DEBUG("Mounter has signaled! Great success!\n");
	
	free(mountPathUTF8);
	free(canonicalMountPath);
	free(canonicalMountpoint);
      }
      //CFRunLoopRun();
    }
    DEBUG("Run loop done.\n");
    if(mountPath != NULL)
      CFRelease(mountPath);
    
    return 0; // We have previously checked that the return value from the child process is 0. We can't get here if it isn't.
  }
  else { // forkRetval == 0
    // Child process
    const int childargc = argc-3;
    char *childargv[childargc+1];
    childargv[childargc] = NULL; // Null terminated
    int i;
    for(i = 0; i < childargc; ++i)
      childargv[i] = argv[i+(argc-childargc)];
    
    if(DEBUGMODE) {
      DEBUG("Contents of argv:\n");
      for(i = 0; i < argc; ++i)
	DEBUG("  argv[%i]: \"%s\"\n", i, argv[i]);
      
      DEBUG("Contents of childargv:\n");
      for(i = 0; i < childargc; ++i)
	DEBUG("  childargv[%i]: \"%s\"\n", i, childargv[i]);
    }
    
    execvp(mount_command, childargv);
    fprintf(stderr, "Could not execute %s!\n", argv[3]);
    return -1;
  }
}
/* ------------------------------------------------------------------------------------------------------

SetDateValueFromFITSHeader: read FITS header and set striped string value to attribute name.

------------------------------------------------------------------------------------------------------ */
void SetDateValueFromFITSHeader(const char* filename, 
				CFMutableDictionaryRef attributes, 
				const char* importerAttrName, 
				char* mainKeyword, 
				char* secondaryKeyword1,
				char* secondaryKeyword2)
{
	CFMutableStringRef headerValue = CFStringCreateMutable(kCFAllocatorDefault, (CFIndex)80);

	CFStringRef value1 = getCleanedHeaderValue(filename, mainKeyword);
	if (value1 != NULL) {
		CFIndex valueLength1 = CFStringGetLength(value1);
		if ((valueLength1 >= 19) || (valueLength1 == 10)) {
			CFStringAppend(headerValue, value1); // value of mainKeyword looks OK.
		}
		CFRelease(value1);
	}

	CFStringRef value2 = getCleanedHeaderValue(filename, secondaryKeyword1);
	if (value2 != NULL) {
		CFIndex valueLength2 = CFStringGetLength(value2);
		CFIndex headerValueLength = CFStringGetLength(headerValue);
		if (headerValueLength == 0 && valueLength2 >= 10) {
			CFStringAppend(headerValueLength, value2); // Append value of secondaryKeyword1
		}
		else if (headerValueLength < 19 && valueLength2 >= 19) {
			CFRange range = CFRangeMake(0, headerValueLength);
			CFStringDelete(headerValue, range);
			CFStringAppend(headerValue, value2); // Replace value of current headerValue by value of secondaryKeyword1
		}
		CFRelease(value2);
	}

	CFStringRef value3 = getCleanedHeaderValue(filename, secondaryKeyword2);
	if (value3 != NULL) {
		CFRange r = CFStringFind(value3, CFSTR(":"), 0);
		CFIndex headerValueLength = CFStringGetLength(headerValue);
		if (headerValueLength == 10 && r.location != kCFNotFound) {
			CFStringAppend(headerValue, CFSTR("T"));
			CFStringAppend(headerValue, value3);
		}
		CFRelease(value3);
	}

	CFIndex headerValueLength = CFStringGetLength(headerValue);
	if (headerValueLength > 0) {
		
		CFArrayRef tArray   = CFStringCreateArrayBySeparatingStrings(kCFAllocatorDefault, headerValue, CFSTR("T"));
		CFArrayRef ymdArray = CFStringCreateArrayBySeparatingStrings(kCFAllocatorDefault, CFArrayGetValueAtIndex(tArray, 0), CFSTR("-"));

		CFGregorianDate gregDate;
		gregDate.year   = 0;
		gregDate.month  = 0;
		gregDate.day    = 0.;
		
		if (CFStringGetLength(CFArrayGetValueAtIndex(ymdArray, 0)) > 0) {
			gregDate.year   = CFStringGetIntValue(CFArrayGetValueAtIndex(ymdArray, 0));
		}
		if (CFStringGetLength(CFArrayGetValueAtIndex(ymdArray, 1)) > 0) {
			gregDate.month  = CFStringGetIntValue(CFArrayGetValueAtIndex(ymdArray, 1));
		}
		if (CFStringGetLength(CFArrayGetValueAtIndex(ymdArray, 2)) > 0) {
			gregDate.day    = CFStringGetIntValue(CFArrayGetValueAtIndex(ymdArray, 2));
		}
		CFRelease(ymdArray);

		gregDate.hour   = 0;
		gregDate.minute = 0;
		gregDate.second = 0.;
		
		CFIndex arraySize = CFArrayGetCount(tArray);
		if (arraySize == 2) {
			CFArrayRef hmsArray = CFStringCreateArrayBySeparatingStrings(kCFAllocatorDefault, CFArrayGetValueAtIndex(tArray, 1), CFSTR(":"));
			if (CFStringGetLength(CFArrayGetValueAtIndex(hmsArray, 0)) > 0) {
				gregDate.hour   = CFStringGetIntValue(CFArrayGetValueAtIndex(hmsArray, 0));
			}
			if (CFStringGetLength(CFArrayGetValueAtIndex(hmsArray, 1)) > 0) {
				gregDate.minute = CFStringGetIntValue(CFArrayGetValueAtIndex(hmsArray, 1));
			}
			if (CFStringGetLength(CFArrayGetValueAtIndex(hmsArray, 2)) > 0) {
				gregDate.second = CFStringGetDoubleValue(CFArrayGetValueAtIndex(hmsArray, 2));			
			}
			CFRelease(hmsArray);
		}

		CFRelease(tArray);

//		printf("%i %i %i %i %i %f\n\n", gregDate.year, gregDate.month, gregDate.day, gregDate.hour, gregDate.minute, gregDate.second);

		if ((gregDate.year > 0) && (gregDate.month > 0) && (gregDate.day > 0)) {
			CFTimeZoneRef timeZone = CFTimeZoneCreateWithName(kCFAllocatorDefault, CFSTR("GMT"), true);
			CFAbsoluteTime absTime = CFGregorianDateGetAbsoluteTime(gregDate, timeZone);
			CFDateRef date = CFDateCreate(kCFAllocatorDefault, absTime);

			if (timeZone != NULL) {
				CFRelease(timeZone);
			}

			CFStringRef cfImporterAttrName = CFStringCreateWithCString(kCFAllocatorDefault, importerAttrName, kCFStringEncodingUTF8);
			CFDictionaryAddValue(attributes, cfImporterAttrName, date);
			CFRelease(cfImporterAttrName);
			CFRelease(date);
		}
	}

	CFRelease(headerValue);
}
Пример #12
0
int main(int argc, char** argv) {
  LOG_DEBUG("[Debug messages enabled]\n");
  if(argc < 4) {
    PrintUsage(stdout);
    return 0;
  }
  
  /* <argv parsing> */
  
  /* Parse argument: mountpoint (CFString) */
  /*CFStringRef mountpoint = CFStringCreateWithCString(kCFAllocatorDefault, 
    argv[1], kCFStringEncodingUTF8); */
  /* Parse argument: mountpointRaw (char*) */
  char *mountpointRaw = argv[1];
  
  /* Parse argument: timeout (double) */
  double timeout;
  {
    CFStringRef timeoutString = CFStringCreateWithCString(kCFAllocatorDefault, 
							  argv[2],
							  kCFStringEncodingUTF8);
    timeout = CFStringGetDoubleValue(timeoutString);
    CFRelease(timeoutString);
    if(timeout == 0.0) {
      fprintf(stdout, "Invalid argument: timeout (\"%s\")\n", argv[2]);
      return -1;
    }
  }
  
  /* Parse argument: mount_command */
  const char *mount_command = argv[3];
  
  /* </argv parsing> */
 
  CFNotificationCenterRef centerRef;
  CFStringRef notificationObjectName = CFSTR(FUSE_LISTEN_OBJECT);
  CFStringRef notificationName = CFSTR(FUSE_MOUNT_NOTIFICATION_NAME);
  //CFRunLoopRef runLoop;
  
  if(DEBUGMODE) {
    LOG_DEBUG("Testing NotificationCallback...\n");
    NotificationCallback(NULL, NULL, notificationObjectName, NULL, NULL);
    LOG_DEBUG("Test completed. Adding observer...\n");
  }
  
  centerRef = CFNotificationCenterGetDistributedCenter();
  //runLoop = CFRunLoopGetCurrent();
  
  /*
   * We need to add ourselves as an observer before the fork, in case the forked
   * process outruns us.
   *
   * Will the child process also be an observer? Possibly, but even if that is
   * the case, it shouldn't seem to make any practical difference (we never
   * enter a CFRunLoop in the child process, at least not before execvp).
   */
  CFNotificationCenterAddObserver(centerRef, NULL, NotificationCallback, notificationName,
				  notificationObjectName, CFNotificationSuspensionBehaviorDrop);

  int forkRetval = fork();
  if(forkRetval == -1) {
    fprintf(stderr, "Could not fork!\n");
    return -1;
  }
  else if(forkRetval != 0) {
    // Parent process
    int childProcessPID = forkRetval;

    int waitpid_status = 0;
    LOG_DEBUG("Waiting for PID %d...\n", childProcessPID);
    int waitpidres = waitpid(childProcessPID, &waitpid_status, 0);
    if(waitpidres == childProcessPID) {
      if(!WIFEXITED(waitpid_status)) {
	LOG_DEBUG("Child process did not exit cleanly! Returning -1.");
	return -1;
      }
      
      int retval = WEXITSTATUS(waitpid_status);
      LOG_DEBUG("PID %d returned with exit code: %d Exiting fuse_wait with this exit code...\n", childProcessPID, retval);
      if(retval != 0) {
	LOG_DEBUG("Exit value indicates an error while executing mount command. Returning without\n");
	LOG_DEBUG("waiting for notification.\n");
	LOG_DEBUG("Returning retval: %d\n", retval);
	return retval;
      }
    }
    else {
      LOG_DEBUG("Abnormal termination of process %d. waitpid returned: %d\n", childProcessPID, waitpidres);
      return -1;
    }
    
    LOG_DEBUG("Running run loop a long time...\n");
    CFStringRef mountPathSnapshot = NULL;
    while(mountPathSnapshot == NULL) {
#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
      int crlrimRetval = CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, true);
      LOG_DEBUG("Exited from run loop. Let's find out why... crlrimRetval: %d "
		"(handled: %d)\n", crlrimRetval, kCFRunLoopRunHandledSource);
#else
      CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, true);
      LOG_DEBUG("Exited from run loop. Let's find out why.\n");
#endif
      mountPathSnapshot = mountPath; // Might have been modified during RunLoop.
#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
      if(crlrimRetval != kCFRunLoopRunHandledSource) {
	fprintf(stderr, "Did not receive a signal within %f seconds. "
		"Exiting...\n", timeout);
	return -2;
      }
#endif
      if(mountPathSnapshot != NULL) {
	LOG_DEBUG("mountPathSnapshot: %p\n", mountPathSnapshot);
	
	/*
	 * Convert the CFString that we got back from the CFNotificationCenter
	 * into UTF-8 data. (We assume that UTF-8 is the encoding used by the
	 * system, which is true in Mac OS X.)
	 */
	CFDataRef utf8Data =
	  CFStringCreateExternalRepresentation(NULL, mountPath,
					       kCFStringEncodingUTF8, 0);
	
	if(utf8Data != NULL) {
	  /*
	   * Put the UTF-8 data in a NULL-terminated C-string.
	   */
	  size_t mountPathUTF8Length = CFDataGetLength(utf8Data) + 1;
	  char *mountPathUTF8 = calloc(1, mountPathUTF8Length);
	  CFDataGetBytes(utf8Data, CFRangeMake(0, mountPathUTF8Length - 1),
			 (unsigned char*) mountPathUTF8);
	  
	  //int mountPathUTF8Length = CFStringGetLength(mountPath) + 1;  // null terminator
	  //char *mountPathUTF8 = malloc(mountPathUTF8Length);
	  //memset(mountPathUTF8, 0, mountPathUTF8Length);
	  //GetCorruptedMacFUSEStringAsUTF8(mountPath, mountPathUTF8, mountPathUTF8Length);
	  
	  /*
	   * Canonicalize the two paths, in case one of the pathnames contain
	   * symbolic links, but not the other.
	   */
	  char *canonicalMountPath = malloc(PATH_MAX);
	  char *canonicalMountpoint = malloc(PATH_MAX);
	  memset(canonicalMountPath, 0, PATH_MAX);
	  memset(canonicalMountpoint, 0, PATH_MAX);
	  
	  realpath(mountPathUTF8, canonicalMountPath);
	  realpath(mountpointRaw, canonicalMountpoint);
	  
	  /*
	   * Finally, compare the two paths for equality.
	   */
	  int cmpres = strncmp(canonicalMountPath, canonicalMountpoint, PATH_MAX);
	  
	  if(cmpres != 0) {
	    LOG_DEBUG("Strings NOT equal. cmpres=%d\n", cmpres);
	    LOG_DEBUG("mountPath (UTF-8): \"%s\"\n", canonicalMountPath);
	    LOG_DEBUG("mountpoint (UTF-8): \"%s\"\n", canonicalMountpoint);
	    
	    mountPathSnapshot = NULL;
	  }
	  else
	    LOG_DEBUG("Recieved a signal for the mount point.\n");
	  
	  free(mountPathUTF8);
	  free(canonicalMountPath);
	  free(canonicalMountpoint);
	}

	CFRelease(utf8Data);
      }
    }
    LOG_DEBUG("Run loop done.\n");
    if(mountPath != NULL)
      CFRelease(mountPath);
    
    return 0; // We have previously checked that the return value from the child process is 0. We can't get here if it isn't.
  }
  else { // forkRetval == 0
    // Child process
    const int childargc = argc-3;
    char *childargv[childargc+1];
    childargv[childargc] = NULL; // Null terminated
    int i;
    for(i = 0; i < childargc; ++i)
      childargv[i] = argv[i+(argc-childargc)];
    
    LOG_DEBUG("Contents of argv:\n");
    for(i = 0; i < argc; ++i)
      LOG_DEBUG("  argv[%i]: \"%s\"\n", i, argv[i]);
    
    LOG_DEBUG("Contents of childargv:\n");
    for(i = 0; i < childargc; ++i)
      LOG_DEBUG("  childargv[%i]: \"%s\"\n", i, childargv[i]);
    
    execvp(mount_command, childargv);
    fprintf(stderr, "Could not execute %s!\n", argv[3]);
    return -1;
  }
}