/*=============================================================================
 *  CAFileHandling.cpp
 
	$Log: CAFileHandling.cpp,v $
	Revision 1.15  2004/12/16 20:11:56  bills
	check that URL is correct
	
	Revision 1.14  2004/06/02 19:59:32  bills
	CreateTrees Needs to know if it should search for Network Dir
	
	Revision 1.13  2004/05/26 17:21:01  luke
	[3656248] allow constructor option to search Network
	
	Revision 1.12  2003/12/02 01:11:43  luke
	factor subclasses out into separate files.  AUPresetFile stays public, others are private.
	
	Revision 1.11  2003/11/06 22:23:26  luke
	Subdirectory construction needs to happen in the base class
	
	Revision 1.10  2003/11/06 19:20:07  luke
	all subclasses need to call CreateTrees()
	
	Revision 1.9  2003/10/22 21:48:32  luke
	add interface to get a particular tree's path
	
	Revision 1.8  2003/10/18 00:57:28  luke
	fix infinite-recursion bug in subclasses
	
	Revision 1.7  2003/10/14 19:16:51  luke
	(GetItemNameCopy + GetDirectoryNameCopy) -> GetNameCopy
	
	Revision 1.6  2003/10/14 18:19:39  luke
	pull GetDirectoryNameCopy() functionality from GetItemNameCopy()
	
	Revision 1.5  2003/10/14 17:18:46  luke
	add context-awareness & fix spelling errors
	
	Revision 1.4  2003/10/11 22:58:08  bills
	add support for au effect chains
	
	Revision 1.3  2003/10/05 23:14:54  bills
	tweaks and add support for MIDIThru
	
	Revision 1.2  2003/10/04 07:06:03  bills
	further additions
	
	Revision 1.1  2003/10/04 01:42:12  bills
	initial checkin
	

 *  Created by William Stewart on Thu Oct 02 2003.
 *  Copyright (c) 2003 Apple Computer. All rights reserved.
==============================================================================*/

#include "CAFileHandling.h"
#include "CACFDictionary.h"

/*
  kLocalDomain                  = -32765, // Domain from '/'
  kNetworkDomain                = -32764, // Domain from '/Network/
  kUserDomain                   = -32763, // Domain from '~/'
*/

const CFStringRef	CAFileHandling::kItemNameKey = CFSTR("name");

CAFileHandling::CAFileHandling (CFStringRef inSubDir, bool inShouldSearchNetwork)
	: mHasLocalDir (false), mHasNetworkDir(false), mHasUserDir(false),
	  mLocalTree (NULL), mUserTree (NULL), mNetworkTree (NULL),
	  mSubDirName (inSubDir)
{
	mHasLocalDir = FindSpecifiedDir (kLocalDomain, inSubDir, mLocalDir) == noErr;
	mHasUserDir = FindSpecifiedDir (kUserDomain, inSubDir, mUserDir) == noErr;
	if (inShouldSearchNetwork)
		mHasNetworkDir = FindSpecifiedDir (kNetworkDomain, inSubDir, mNetworkDir) == noErr;
}

CAFileHandling::~CAFileHandling() 
{
	if (mLocalTree)
		CFRelease (mLocalTree);
	if (mUserTree)
		CFRelease (mUserTree);
	if (mNetworkTree)
		CFRelease (mNetworkTree);
}

OSStatus	CAFileHandling::FindSpecifiedDir (SInt16 inDomain, CFStringRef inAudioSubDirName, FSRef &outDir, bool inCreateDir)
{
	OSStatus result;
	FSRef parentDir;
	if (result = FSFindFolder (inDomain, kAudioSupportFolderType, (Boolean)inCreateDir, &parentDir))
		return result;

	if (result = FindSpecifiedDir (parentDir, inAudioSubDirName, outDir, inCreateDir)) 
	{
		return result;
	}
	return noErr;
}

OSStatus	CAFileHandling::FindSpecifiedDir (const FSRef &inParentDir, CFStringRef inSubDirName, FSRef &outDir, bool inCreateDir)
{
	UniChar chars[256];
	CFRange range;
	range.location = 0;
	range.length = 256;
	
	CFStringGetCharacters (inSubDirName, range, chars);
		
	OSStatus result;
	if (result = FSMakeFSRefUnicode (&inParentDir, CFStringGetLength (inSubDirName), 
						chars, kTextEncodingDefaultFormat, &outDir))
	{
		if (result == fnfErr && inCreateDir) {
			result = FSCreateDirectoryUnicode (&inParentDir, CFStringGetLength(inSubDirName), chars, 
							kFSCatInfoNone, NULL, &outDir, NULL, NULL);		
		}
		return result;
	}
	
	return noErr;
}

void		CAFileHandling::SetUserDir (FSRef *inRef)
{
	if (inRef) {
		mUserDir = *inRef;
		mHasUserDir = true;
	} else {
		mHasUserDir = false;
	}
}

void		CAFileHandling::SetLocalDir (FSRef *inRef)
{
	if (inRef) {
		mLocalDir = *inRef;
		mHasLocalDir = true;
	} else {
		mHasLocalDir = false;
	}
}

void		CAFileHandling::SetNetworkDir (FSRef *inRef)
{
	if (inRef) {
		mNetworkDir = *inRef;
		mHasNetworkDir = true;
	} else {
		mHasNetworkDir = false;
	}
}

OSStatus		CAFileHandling::CreateSubDirectories (FSRef &inRef, SInt16 inDomain)
{
	switch (inDomain) {
		case kUserDomain: SetUserDir (&inRef); break;
		case kLocalDomain: SetLocalDir (&inRef); break;
		case kNetworkDomain: SetNetworkDir (&inRef); break;
	}
    
	return noErr;
}

void TreeShow (const void *value, void *context)
{
	if (value) {
		CAFileHandling* This = (CAFileHandling*)context;
		This->ShowEntireTree ((CFTreeRef)value);
	}
}

void		CAFileHandling::ShowEntireTree (CFTreeRef inTree)
{
	CFShow (inTree);
	if (inTree)
		CFTreeApplyFunctionToChildren (inTree, TreeShow, this);
}

CFTreeRef 	CAFileHandling::AddFileItemToTree (const FSRef &inRef, CFTreeRef inParentTree)
{
	CFTreeRef newTree = CreateTree (inRef);
	CFTreeAppendChild (inParentTree, newTree);
#if 0
	CFShow (newTree);
#endif
	CFRelease(newTree);

	return newTree;
}



void		CAFileHandling::CreateTrees (bool inShouldSearchNetwork)
{
	mLocalTree = CreateNewTree (GetLocalDir(), mLocalTree);
	if (inShouldSearchNetwork)
		mNetworkTree = CreateNewTree (GetNetworkDir(), mNetworkTree);
	mUserTree = CreateNewTree (GetUserDir(), mUserTree);
}
		
CFTreeRef 		CAFileHandling::CreateNewTree (const FSRef* inRef, CFTreeRef inTree)
{
	if (inRef) {
		if (inTree)
			CFRelease (inTree);
		CFTreeRef tree = CreateTree (*inRef); 
		Scan (*inRef, tree);
		return tree;
	}
	return NULL;
}

CFTreeRef	CAFileHandling::CreateTree (const FSRef &inRef)
{
	CFURLRef url = CFURLCreateFromFSRef (kCFAllocatorDefault, &inRef);
	return CreateTree (url);
}

CFTreeRef	CAFileHandling::CreateTree (CFURLRef inURL)
{
	CFTreeContext treeContext;

	treeContext.version = 0;
	treeContext.info = (void*)inURL;
	treeContext.retain = CFRetain;
	treeContext.release = CFRelease;
	treeContext.copyDescription = CFCopyDescription;

	CFTreeRef tree = CFTreeCreate (kCFAllocatorDefault, &treeContext);
	CFRelease(inURL);
	
	return tree;
}

void		CAFileHandling::Scan (const FSRef &inParentDir, CFTreeRef inParentTree)
{		
	OSStatus result;
	FSIterator iter;

	if (FSOpenIterator (&inParentDir, kFSIterateFlat, &iter))
		return;

	do {
		ItemCount numItems = 0;

		const FSCatalogInfoBitmap whichInfo = kFSCatInfoNodeFlags;

		FSCatalogInfo catalogInfo;
		FSRef theFSRef;
		result = FSGetCatalogInfoBulk (iter, 1, &numItems, NULL, whichInfo, &catalogInfo, &theFSRef, NULL, NULL);

		if (!result && (numItems > 0)) 
		{
			if (catalogInfo.nodeFlags & kFSNodeIsDirectoryMask)
			{
				// WE FOUND A SUB DIRECTORY
				CFTreeRef newSubTree = AddFileItemToTree (theFSRef, inParentTree);
#if 0
				printf ("\n* * * ADDING DIR * * *\n");
				CFShow (newSubTree);
#endif
				Scan (theFSRef, newSubTree);
			}
			else
			{
				//WE FOUND A FILE
				CFURLRef fileURL = CFURLCreateFromFSRef (kCFAllocatorDefault, &theFSRef);
				CFStringRef fNameExt = CFURLCopyPathExtension (fileURL);
				bool matches = false;
				if (fNameExt) {
					matches = CFStringCompare(fNameExt, GetExtension(), kCFCompareCaseInsensitive) == kCFCompareEqualTo;
					CFRelease (fNameExt);
				}
				CFRelease (fileURL);
				if (matches) {
#if 0
					printf ("* * * ADDING FILE * * *\n");
#endif
					AddFileItemToTree (theFSRef, inParentTree);
				}
			}
		}
	} while (!result);

	// clean up
	FSCloseIterator (iter);
}

bool			CAFileHandling::IsDirectory (CFTreeRef inTree) const
{
	CFTreeContext context;
	CFTreeGetContext (inTree, &context);

	return CFURLHasDirectoryPath ((CFURLRef)context.info);
}

bool			CAFileHandling::IsItem (CFTreeRef inTree) const
{
	CFTreeContext context;
	CFTreeGetContext (inTree, &context);

	CFStringRef fNameExt = CFURLCopyPathExtension ((CFURLRef)context.info);
	if (fNameExt) {
		bool matches = CFStringCompare(fNameExt, GetExtension(), kCFCompareCaseInsensitive) == kCFCompareEqualTo;
		CFRelease (fNameExt);
		return matches;
	}
	return false;
}

bool	CAFileHandling::IsUserContext (CFTreeRef inTree)
{
	CFTreeContext context;
	CFTreeGetContext (inTree, &context);
	CFURLRef URL = (CFURLRef)context.info;
    
    CFURLRef userURL = CFURLCreateFromFSRef (NULL, &mUserDir);
    if (userURL && URL)
        if (CFStringHasPrefix (CFURLGetString(URL), CFURLGetString(userURL)))
            return true;
    
    return false;
}

bool	CAFileHandling::IsNetworkContext (CFTreeRef inTree)
{
	CFTreeContext context;
	CFTreeGetContext (inTree, &context);
	CFURLRef URL = (CFURLRef)context.info;
    
    CFURLRef networkURL = CFURLCreateFromFSRef (NULL, &mNetworkDir);
    if (networkURL && URL)
        if (CFStringHasPrefix (CFURLGetString(URL), CFURLGetString(networkURL)))
            return true;
    
    return false;
}

bool	CAFileHandling::IsLocalContext (CFTreeRef inTree)
{
	CFTreeContext context;
	CFTreeGetContext (inTree, &context);
	CFURLRef URL = (CFURLRef)context.info;
    
    CFURLRef localURL = CFURLCreateFromFSRef (NULL, &mLocalDir);
    if (localURL && URL)
        if (CFStringHasPrefix (CFURLGetString(URL), CFURLGetString(localURL)))
            return true;
    
    return false;
}

OSStatus		CAFileHandling::CreateUserDirectories ()
{
	if (GetUserDir() && mUserTree)
		return noErr;
	
	OSStatus result;
	if (result = FindSpecifiedDir (kUserDomain, mSubDirName, mUserDir, true))
		return result;
	
	if (result = CreateSubDirectories (mUserDir, kUserDomain))
		return result;

	mUserTree = CreateNewTree (GetUserDir(), mUserTree);

	return noErr;	
}

OSStatus		CAFileHandling::CreateLocalDirectories ()
{
	if (GetLocalDir() && mLocalTree)
		return noErr;
	
	OSStatus result;
	if (result = FindSpecifiedDir (kLocalDomain, mSubDirName, mLocalDir, true))
		return result;
	
	if (result = CreateSubDirectories (mLocalDir, kLocalDomain))
		return result;

	mLocalTree = CreateNewTree (GetLocalDir(), mLocalTree);

	return noErr;	
}

OSStatus		CAFileHandling::CreateNetworkDirectories ()
{
	if (GetNetworkDir() && mNetworkTree)
		return noErr;
	
	OSStatus result;
	if (result = FindSpecifiedDir (kNetworkDomain, mSubDirName, mNetworkDir, true))
		return result;
	
	if (result = CreateSubDirectories (mNetworkDir, kNetworkDomain))
		return result;

	mNetworkTree = CreateNewTree (GetNetworkDir(), mNetworkTree);

	return noErr;	
}

OSStatus		CAFileHandling::CreateDirectory (CFTreeRef inDirTree, CFStringRef inDirName, CFTreeRef *outTree)
{
	if (IsDirectory (inDirTree) == false)
		return -1;

	CFTreeContext context;
	CFTreeGetContext (inDirTree, &context);

	FSRef parentDir;	
	if (CFURLGetFSRef((CFURLRef)context.info, &parentDir))
		return -1;
	
	FSRef subDir;
	OSStatus result;
	if (result = FindSpecifiedDir (parentDir, inDirName, subDir, true))
		return result;
	
	CFTreeRef newTree;
	newTree = CreateTree (subDir);
	CFTreeAppendChild (inDirTree, newTree);
	
	if (outTree)
		*outTree = newTree;
		
	return noErr;
}


bool		CAFileHandling::ValidPropertyList (CFPropertyListRef inData)
{
    // is our data good? (i.e., is it an instance of CFPropertyListRef?)
    if (!(	CFPropertyListIsValid (inData, kCFPropertyListOpenStepFormat)		||
            CFPropertyListIsValid (inData, kCFPropertyListXMLFormat_v1_0)		||
            CFPropertyListIsValid (inData, kCFPropertyListBinaryFormat_v1_0)	))
	{
		return false;
    }
	return true;
}

OSStatus		CAFileHandling::SaveInDirectory (CFTreeRef inParentTree, CFStringRef inFileName, CFPropertyListRef inData)
{
	CFTreeContext context;
	CFTreeGetContext (inParentTree, &context);
    
	if (CFURLHasDirectoryPath ((CFURLRef)context.info) == false)
		return paramErr;
	
	if (ValidPropertyList (inData) == false)
		return -1;
	
	CFPropertyListRef plist;
    OSStatus result;
	if (result = PremassageDataToSave (inData, inFileName, plist))
		return result;
    
	CFMutableStringRef fileName = NULL;
	CFURLRef fileURL = NULL;
	
    // Convert the property list into XML data.
	CFDataRef xmlData = CFPropertyListCreateXMLData (kCFAllocatorDefault, plist);
	if (xmlData == NULL) {
		result = paramErr;
		goto home;
	}
	
	fileName = CFStringCreateMutableCopy (kCFAllocatorDefault, 1024, inFileName);
	UniChar dot;
	dot = '.';
	CFStringAppendCharacters (fileName, &dot, 1);
	CFStringAppend (fileName, GetExtension());
	
	fileURL = CFURLCreateCopyAppendingPathComponent (kCFAllocatorDefault, (CFURLRef)context.info, fileName, false);

	// Write the XML data to the file.
	Boolean status;
	status = CFURLWriteDataAndPropertiesToResource (fileURL, xmlData, NULL, &result);
    
	CFRelease(xmlData);
	
	if (status == false || result) {
		CFRelease(fileURL);
		if (!result) result = -1;
		goto home;
	}
	
	CFTreeRef newTree;
	newTree = CreateTree (fileURL); //this releases the URL
	CFTreeAppendChild (inParentTree, newTree);

home:
	if (fileName)
		CFRelease (fileName);
	PostmassageSavedData (plist);
	return result;
}
	
OSStatus		CAFileHandling::ReadFromTreeLeaf (CFTreeRef inTreeLeaf, CFPropertyListRef &outData, CFStringRef *outErrString) const
{
	CFTreeContext context;
	CFTreeGetContext (inTreeLeaf, &context);

	if (CFURLHasDirectoryPath ((CFURLRef)context.info))
		return paramErr;
	
	CFDataRef         resourceData = NULL;
	SInt32            result;
    
   // Read the XML file.
   Boolean status = CFURLCreateDataAndPropertiesFromResource (kCFAllocatorDefault, (CFURLRef)context.info,
                                                                &resourceData,	// place to put file data
                                                                NULL, NULL, &result);
        if (status == false || result) {
            if (resourceData) 
				CFRelease (resourceData);
            return result;
        }
    
	CFStringRef errString = NULL;
	CFPropertyListRef theData = CFPropertyListCreateFromXMLData (kCFAllocatorDefault, resourceData,  
													kCFPropertyListImmutable, &errString);
        if (theData == NULL || errString) {
            if (resourceData) 
				CFRelease (resourceData);
			if (errString) {
				if (outErrString)
					*outErrString = errString;
				else
					CFRelease (errString);
			}
            return -1;
       }
		    
	CFRelease (resourceData);
    
	PostProcessReadData (theData, outData);
	
	return noErr;
}

OSStatus		CAFileHandling::GetNameCopy (CFTreeRef inTree, CFStringRef &outName) const
{
    // first check for directory
    CFTreeContext context;
    CFTreeGetContext (inTree, &context);
    
    CFURLRef url = (CFURLRef)context.info;
    if (!url) return paramErr;
    if (CFURLHasDirectoryPath (url)) {
        outName = CFURLCopyLastPathComponent (url);
        return noErr;
    }
    
    // else check for item name
    CFPropertyListRef data;
	OSStatus result;
    if ((result = ReadFromTreeLeaf (inTree, data)) == noErr) {
		if (CFGetTypeID(data) == CFDictionaryGetTypeID()) {
			CFStringRef name = (CFStringRef)CFDictionaryGetValue ((CFDictionaryRef)data, kItemNameKey);
			if (name) {
				CFRetain (name);
				outName = name;
			}
			CFRelease (data);
			return name ? noErr : OSStatus(paramErr);
		}
		return paramErr;
	}
	return result;
}

OSStatus		CAFileHandling::GetPathCopy (CFTreeRef inTree, CFURLRef &outURL) const
{
    // first check for directory
    CFTreeContext context;
    CFTreeGetContext (inTree, &context);
    
    CFURLRef url = (CFURLRef)context.info;
    CFRetain(url);
    outURL = url;
    return noErr;
}
