/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 2001, 2002
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *  Conrad Carlen <ccarlen@netscape.com>
 *  Jungshik Shin <jshin@mailaps.org>
 *  Asaf Romano <mozilla.mano@sent.com>
 *  Mark Mentovai <mark@moxienet.com>
 *  Josh Aas <josh@mozilla.com>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#include "nsLocalFile.h"
#include "nsDirectoryServiceDefs.h"

#include "nsObjCExceptions.h"
#include "nsAutoPtr.h"
#include "nsString.h"
#include "nsReadableUtils.h"
#include "nsIDirectoryEnumerator.h"
#include "nsISimpleEnumerator.h"
#include "nsITimelineService.h"
#include "nsVoidArray.h"
#include "nsTArray.h"

#include "plbase64.h"
#include "prmem.h"
#include "nsCRT.h"
#include "nsHashKeys.h"

#include "nsTArray.h"
#include "nsTraceRefcntImpl.h"

#include <Carbon/Carbon.h>
#import <Cocoa/Cocoa.h>

#include <unistd.h>
#include <sys/stat.h>
#include <stdlib.h>

#define CHECK_mBaseRef()                        \
    PR_BEGIN_MACRO                              \
        if (!mBaseRef)                          \
            return NS_ERROR_NOT_INITIALIZED;    \
    PR_END_MACRO

static nsresult MacErrorMapper(OSErr inErr);
static void CopyUTF8toUTF16NFC(const nsACString& aSrc, nsAString& aResult);

#pragma mark -
#pragma mark [FSRef operator==]

bool operator==(const FSRef& lhs, const FSRef& rhs)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;

  return (::FSCompareFSRefs(&lhs, &rhs) == noErr);

  NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
}

#pragma mark -
#pragma mark [StFollowLinksState]

class StFollowLinksState
{
  public:
    StFollowLinksState(nsLocalFile& aFile) :
        mFile(aFile)
    {
        mFile.GetFollowLinks(&mSavedState);
    }

    StFollowLinksState(nsLocalFile& aFile, PRBool followLinksState) :
        mFile(aFile)
    {
        mFile.GetFollowLinks(&mSavedState);
        mFile.SetFollowLinks(followLinksState);
    }

    ~StFollowLinksState()
    {
        mFile.SetFollowLinks(mSavedState);
    }
    
  private:
    nsLocalFile& mFile;
    PRBool mSavedState;
};

#pragma mark -
#pragma mark [nsDirEnumerator]

class nsDirEnumerator : public nsISimpleEnumerator,
                        public nsIDirectoryEnumerator
{
    public:

        NS_DECL_ISUPPORTS

        nsDirEnumerator() :
          mIterator(nsnull),
          mFSRefsArray(nsnull),
          mArrayCnt(0), mArrayIndex(0)
        {
        }

        nsresult Init(nsILocalFileMac* parent) 
        {
          NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

          NS_ENSURE_ARG(parent);
          
          OSErr err;
          nsresult rv;
          FSRef parentRef;
          
          rv = parent->GetFSRef(&parentRef);
          if (NS_FAILED(rv))
            return rv;
          
          mFSRefsArray = (FSRef *)nsMemory::Alloc(sizeof(FSRef)
                                                  * kRequestCountPerIteration);
          if (!mFSRefsArray)
            return NS_ERROR_OUT_OF_MEMORY;
          
          err = ::FSOpenIterator(&parentRef, kFSIterateFlat, &mIterator);
          if (err != noErr)
            return MacErrorMapper(err);
                              
          return NS_OK;

          NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
        }

        NS_IMETHOD HasMoreElements(PRBool *result) 
        {
          NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

          if (!mIterator || !mFSRefsArray) {
            *result = PR_FALSE;
            return NS_OK;
          }

          if (mNext == nsnull) {
            if (mArrayIndex >= mArrayCnt) {
              ItemCount actualCnt;
              OSErr err = ::FSGetCatalogInfoBulk(mIterator,
                                           kRequestCountPerIteration,
                                           &actualCnt,
                                           nsnull,
                                           kFSCatInfoNone,
                                           nsnull,
                                           mFSRefsArray,
                                           nsnull,
                                           nsnull);
            
              if (err == noErr || err == errFSNoMoreItems) {
                mArrayCnt = actualCnt;
                mArrayIndex = 0;
              }
            }

            if (mArrayIndex < mArrayCnt) {
              nsRefPtr<nsLocalFile> newFile = new nsLocalFile;
              if (!newFile)
                return NS_ERROR_OUT_OF_MEMORY;
              FSRef fsRef = mFSRefsArray[mArrayIndex];
              if (NS_FAILED(newFile->InitWithFSRef(&fsRef)))
                return NS_ERROR_FAILURE;
              mArrayIndex++;
              mNext = newFile;
            } 
          }

          *result = mNext != nsnull;
          if (!*result)
            Close();

          return NS_OK;

          NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
        }

        NS_IMETHOD GetNext(nsISupports **result) 
        {
            NS_ENSURE_ARG_POINTER(result);
            *result = nsnull;

            nsresult rv;
            PRBool hasMore;
            rv = HasMoreElements(&hasMore);
            if (NS_FAILED(rv)) return rv;

            *result = mNext; // might return nsnull
            NS_IF_ADDREF(*result);

            mNext = nsnull;
            return NS_OK;
        }

        NS_IMETHOD GetNextFile(nsIFile **result)
        {
            *result = nsnull;
            PRBool hasMore = PR_FALSE;
            nsresult rv = HasMoreElements(&hasMore);
            if (NS_FAILED(rv) || !hasMore)
                return rv;
            *result = mNext;
            NS_IF_ADDREF(*result);
            mNext = nsnull;
            return NS_OK;
        }

        NS_IMETHOD Close()
        {
          NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

          if (mIterator) {
            ::FSCloseIterator(mIterator);
            mIterator = nsnull;
          }
          if (mFSRefsArray) {
            nsMemory::Free(mFSRefsArray);
            mFSRefsArray = nsnull;
          }
          return NS_OK;

          NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
        }

    private:
        ~nsDirEnumerator() 
        {
          Close();
        }

    protected:
        // According to Apple doc, request the number of objects
        // per call that will fit in 4 VM pages.
        enum {
          kRequestCountPerIteration = ((4096 * 4) / sizeof(FSRef))
        };
        
        nsCOMPtr<nsILocalFileMac>   mNext;
        
        FSIterator              mIterator;
        FSRef                   *mFSRefsArray;
        PRInt32                 mArrayCnt, mArrayIndex;
};

NS_IMPL_ISUPPORTS2(nsDirEnumerator, nsISimpleEnumerator, nsIDirectoryEnumerator)

#define FILENAME_BUFFER_SIZE 512

const char kPathSepChar = '/';

// The HFS+ epoch is Jan. 1, 1904 GMT - differs from HFS in which times were local
// The NSPR epoch is Jan. 1, 1970 GMT
// 2082844800 is the difference in seconds between those dates
const PRInt64 kJanuaryFirst1970Seconds = 2082844800LL;

#pragma mark -
#pragma mark [CTORs/DTOR]

nsLocalFile::nsLocalFile() :
  mBaseRef(nsnull),
  mTargetRef(nsnull),
  mFollowLinks(PR_TRUE),
  mFollowLinksDirty(PR_TRUE)
{
}

nsLocalFile::nsLocalFile(const nsLocalFile& src) :
  mBaseRef(src.mBaseRef),
  mTargetRef(src.mTargetRef),
  mFollowLinks(src.mFollowLinks),
  mFollowLinksDirty(src.mFollowLinksDirty)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;

  // A CFURLRef is immutable so no need to copy, just retain.
  if (mBaseRef)
    ::CFRetain(mBaseRef);
  if (mTargetRef)
    ::CFRetain(mTargetRef);

  NS_OBJC_END_TRY_ABORT_BLOCK;
}

nsLocalFile::~nsLocalFile()
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;

  if (mBaseRef)
    ::CFRelease(mBaseRef);
  if (mTargetRef)
    ::CFRelease(mTargetRef);

  NS_OBJC_END_TRY_ABORT_BLOCK;
}

#pragma mark -
#pragma mark [nsISupports]

NS_IMPL_THREADSAFE_ISUPPORTS4(nsLocalFile,
                              nsILocalFileMac,
                              nsILocalFile,
                              nsIFile,
                              nsIHashable)
                              
NS_IMETHODIMP nsLocalFile::nsLocalFileConstructor(nsISupports* outer, const nsIID& aIID, void* *aInstancePtr)
{
  NS_ENSURE_ARG_POINTER(aInstancePtr);
  NS_ENSURE_NO_AGGREGATION(outer);

  nsLocalFile* inst = new nsLocalFile();
  if (inst == NULL)
    return NS_ERROR_OUT_OF_MEMORY;
  
  nsresult rv = inst->QueryInterface(aIID, aInstancePtr);
  if (NS_FAILED(rv))
  {
    delete inst;
    return rv;
  }
  return NS_OK;
}

#pragma mark -
#pragma mark [nsIFile]

NS_IMETHODIMP nsLocalFile::Append(const nsAString& aNode)
{
  return AppendNative(NS_ConvertUTF16toUTF8(aNode));
}

NS_IMETHODIMP nsLocalFile::AppendNative(const nsACString& aNode)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  // Check we are correctly initialized.
  CHECK_mBaseRef();

  nsACString::const_iterator start, end;
  aNode.BeginReading(start);
  aNode.EndReading(end);
  if (FindCharInReadable(kPathSepChar, start, end))
    return NS_ERROR_FILE_UNRECOGNIZED_PATH;

  CFStringRef nodeStrRef = ::CFStringCreateWithCString(kCFAllocatorDefault,
                                  PromiseFlatCString(aNode).get(),
                                  kCFStringEncodingUTF8);
  if (nodeStrRef) {
    CFURLRef newRef = ::CFURLCreateCopyAppendingPathComponent(kCFAllocatorDefault,
                                  mBaseRef, nodeStrRef, PR_FALSE);
    ::CFRelease(nodeStrRef);
    if (newRef) {
      SetBaseRef(newRef);
      ::CFRelease(newRef);
      return NS_OK;
    }
  }
  return NS_ERROR_FAILURE;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::Normalize()
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  // Check we are correctly initialized.
  CHECK_mBaseRef();

  // CFURL doesn't doesn't seem to resolve paths containing relative
  // components, so we'll nick the stdlib code from nsLocalFileUnix
  UInt8 path[PATH_MAX] = "";
  Boolean success;
  success = ::CFURLGetFileSystemRepresentation(mBaseRef, true, path, PATH_MAX);
  if (!success)
    return NS_ERROR_FAILURE;

  char resolved_path[PATH_MAX] = "";
  char *resolved_path_ptr = nsnull;
  resolved_path_ptr = realpath((char*)path, resolved_path);

  // if there is an error, the return is null.
  if (!resolved_path_ptr)
      return NSRESULT_FOR_ERRNO();

  // Need to know whether we're a directory to create a new CFURLRef
  PRBool isDirectory;
  nsresult rv = IsDirectory(&isDirectory);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = NS_ERROR_FAILURE;
  CFStringRef pathStrRef =
    ::CFStringCreateWithCString(kCFAllocatorDefault,
                                resolved_path,
                                kCFStringEncodingUTF8);
  if (pathStrRef) {
    CFURLRef newURLRef =
      ::CFURLCreateWithFileSystemPath(kCFAllocatorDefault, pathStrRef,
                                      kCFURLPOSIXPathStyle, isDirectory);
    if (newURLRef) {
      SetBaseRef(newURLRef);
      ::CFRelease(newURLRef);
      rv = NS_OK;
    }
    ::CFRelease(pathStrRef);
  }

  return rv;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::Create(PRUint32 type, PRUint32 permissions)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  if (type != NORMAL_FILE_TYPE && type != DIRECTORY_TYPE)
    return NS_ERROR_FILE_UNKNOWN_TYPE;

  // Check we are correctly initialized.
  CHECK_mBaseRef();
  
  nsTArray<nsString> nonExtantNodes;
  CFURLRef pathURLRef = mBaseRef;
  FSRef pathFSRef;
  CFStringRef leafStrRef = nsnull;
  nsAutoTArray<UniChar, FILENAME_BUFFER_SIZE> buffer;
  Boolean success;
  
  // Work backwards through the path to find the last node which
  // exists. Place the nodes which don't exist in an array and we'll
  // create those below.
  while ((success = ::CFURLGetFSRef(pathURLRef, &pathFSRef)) == false) {
    leafStrRef = ::CFURLCopyLastPathComponent(pathURLRef);
    if (!leafStrRef)
      break;
    CFIndex leafLen = ::CFStringGetLength(leafStrRef);
    if (!buffer.SetLength(leafLen + 1))
      break;
    ::CFStringGetCharacters(leafStrRef, CFRangeMake(0, leafLen), buffer.Elements());
    buffer[leafLen] = '\0';
    nonExtantNodes.AppendElement(nsString(nsDependentString(buffer.Elements())));
    ::CFRelease(leafStrRef);
    leafStrRef = nsnull;
    
    // Get the parent of the leaf for the next go round
    CFURLRef parent = ::CFURLCreateCopyDeletingLastPathComponent(NULL, pathURLRef);
    if (!parent)
      break;
    if (pathURLRef != mBaseRef)
      ::CFRelease(pathURLRef);
    pathURLRef = parent;
  }
  if (pathURLRef != mBaseRef)
    ::CFRelease(pathURLRef);
  if (leafStrRef != nsnull)
    ::CFRelease(leafStrRef);
  if (!success)
    return NS_ERROR_FAILURE;
  PRInt32 nodesToCreate = PRInt32(nonExtantNodes.Length());
  if (nodesToCreate == 0)
    return NS_ERROR_FILE_ALREADY_EXISTS;
  
  OSErr err;    
  nsAutoString nextNodeName;
  for (PRInt32 i = nodesToCreate - 1; i > 0; i--) {
    nextNodeName = nonExtantNodes[i];
    err = ::FSCreateDirectoryUnicode(&pathFSRef,
                                      nextNodeName.Length(),
                                      (const UniChar *)nextNodeName.get(),
                                      kFSCatInfoNone,
                                      nsnull, &pathFSRef, nsnull, nsnull);
    if (err != noErr)
      return MacErrorMapper(err);
  }
  nextNodeName = nonExtantNodes[0];
  if (type == NORMAL_FILE_TYPE) {
    err = ::FSCreateFileUnicode(&pathFSRef,
                                nextNodeName.Length(),
                                (const UniChar *)nextNodeName.get(),
                                kFSCatInfoNone,
                                nsnull, nsnull, nsnull);
  }
  else {
    err = ::FSCreateDirectoryUnicode(&pathFSRef,
                                    nextNodeName.Length(),
                                    (const UniChar *)nextNodeName.get(),
                                    kFSCatInfoNone,
                                    nsnull, nsnull, nsnull, nsnull);
  }
            
  return MacErrorMapper(err);

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::GetLeafName(nsAString& aLeafName)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  nsCAutoString nativeString;
  nsresult rv = GetNativeLeafName(nativeString);
  if (NS_FAILED(rv))
    return rv;
  CopyUTF8toUTF16NFC(nativeString, aLeafName);
  return NS_OK;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::SetLeafName(const nsAString& aLeafName)
{
  return SetNativeLeafName(NS_ConvertUTF16toUTF8(aLeafName));
}

NS_IMETHODIMP nsLocalFile::GetNativeLeafName(nsACString& aNativeLeafName)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  // Check we are correctly initialized.
  CHECK_mBaseRef();

  nsresult rv = NS_ERROR_FAILURE;
  CFStringRef leafStrRef = ::CFURLCopyLastPathComponent(mBaseRef);
  if (leafStrRef) {
    rv = CFStringReftoUTF8(leafStrRef, aNativeLeafName);
    ::CFRelease(leafStrRef);
  }
  return rv;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::SetNativeLeafName(const nsACString& aNativeLeafName)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  // Check we are correctly initialized.
  CHECK_mBaseRef();

  nsresult rv = NS_ERROR_FAILURE;
  CFURLRef parentURLRef = ::CFURLCreateCopyDeletingLastPathComponent(kCFAllocatorDefault, mBaseRef);
  if (parentURLRef) {
    CFStringRef nodeStrRef = ::CFStringCreateWithCString(kCFAllocatorDefault,
                                    PromiseFlatCString(aNativeLeafName).get(),
                                    kCFStringEncodingUTF8);

    if (nodeStrRef) {
      CFURLRef newURLRef = ::CFURLCreateCopyAppendingPathComponent(kCFAllocatorDefault,
                                    parentURLRef, nodeStrRef, PR_FALSE);
      if (newURLRef) {
        SetBaseRef(newURLRef);
        ::CFRelease(newURLRef);
        rv = NS_OK;
      }
      ::CFRelease(nodeStrRef);
    }
    ::CFRelease(parentURLRef);
  }
  return rv;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::CopyTo(nsIFile *newParentDir, const nsAString& newName)
{
  return CopyInternal(newParentDir, newName, PR_FALSE);
}

NS_IMETHODIMP nsLocalFile::CopyToNative(nsIFile *newParentDir, const nsACString& newName)
{
  return CopyInternal(newParentDir, NS_ConvertUTF8toUTF16(newName), PR_FALSE);
}

NS_IMETHODIMP nsLocalFile::CopyToFollowingLinks(nsIFile *newParentDir, const nsAString& newName)
{
  return CopyInternal(newParentDir, newName, PR_TRUE);
}

NS_IMETHODIMP nsLocalFile::CopyToFollowingLinksNative(nsIFile *newParentDir, const nsACString& newName)
{
  return CopyInternal(newParentDir, NS_ConvertUTF8toUTF16(newName), PR_TRUE);
}

NS_IMETHODIMP nsLocalFile::MoveTo(nsIFile *newParentDir, const nsAString& newName)
{
  return MoveToNative(newParentDir, NS_ConvertUTF16toUTF8(newName));
}

NS_IMETHODIMP nsLocalFile::MoveToNative(nsIFile *newParentDir, const nsACString& newName)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  // Check we are correctly initialized.
  CHECK_mBaseRef();

  StFollowLinksState followLinks(*this, PR_FALSE);

  PRBool isDirectory;
  nsresult rv = IsDirectory(&isDirectory);
  if (NS_FAILED(rv))
    return rv;

  // Get the source path.
  nsCAutoString srcPath;
  rv = GetNativePath(srcPath);
  if (NS_FAILED(rv))
    return rv;

  // Build the destination path.
  nsCOMPtr<nsIFile> parentDir = newParentDir;
  if (!parentDir) {
    if (newName.IsEmpty())
      return NS_ERROR_INVALID_ARG;
    rv = GetParent(getter_AddRefs(parentDir));
    if (NS_FAILED(rv))
      return rv;   
  }
  else {
    PRBool exists;
    rv = parentDir->Exists(&exists);
    if (NS_FAILED(rv))
      return rv;
    if (!exists) {
      rv = parentDir->Create(nsIFile::DIRECTORY_TYPE, 0777);
      if (NS_FAILED(rv))
        return rv;
    }
  }

  nsCAutoString destPath;
  rv = parentDir->GetNativePath(destPath);
  if (NS_FAILED(rv))
    return rv;

  if (!newName.IsEmpty())
    destPath.Append(NS_LITERAL_CSTRING("/") + newName);
  else {
    nsCAutoString leafName;
    rv = GetNativeLeafName(leafName);
    if (NS_FAILED(rv))
      return rv;
    destPath.Append(NS_LITERAL_CSTRING("/") + leafName);
  }

  // Perform the move.
  if (rename(srcPath.get(), destPath.get()) != 0) {
    if (errno == EXDEV) {
      // Can't move across volume (device) boundaries.  Copy and remove.
      rv = CopyToNative(parentDir, newName);
      if (NS_SUCCEEDED(rv)) {
        // Permit removal failure.
        Remove(PR_TRUE);
      }
    }
    else
      rv = NSRESULT_FOR_ERRNO();

    if (NS_FAILED(rv))
      return rv;
  }

  // Update |this| to refer to the moved file.
  CFURLRef newBaseRef =
   ::CFURLCreateFromFileSystemRepresentation(NULL, (UInt8*)destPath.get(),
                                             destPath.Length(), isDirectory);
  if (!newBaseRef)
    return NS_ERROR_FAILURE;
  SetBaseRef(newBaseRef);
  ::CFRelease(newBaseRef);

  return rv;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::Remove(PRBool recursive)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  // Check we are correctly initialized.
  CHECK_mBaseRef();

  // XXX If we're an alias, never remove target
  StFollowLinksState followLinks(*this, PR_FALSE);

  PRBool isDirectory;
  nsresult rv = IsDirectory(&isDirectory);
  if (NS_FAILED(rv))
    return rv;

  if (recursive && isDirectory) {
    CFURLRef urlRef;
    rv = GetCFURL(&urlRef);
    if (NS_SUCCEEDED(rv) && urlRef) {
      NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init];
      BOOL removeSuccess = [[NSFileManager defaultManager] removeFileAtPath:[(NSURL*)urlRef path] handler:nil];
      [ap release];
      ::CFRelease(urlRef);
      rv = removeSuccess ? NS_OK : NS_ERROR_FAILURE;
    }
  }
  else {
    nsCAutoString path;
    rv = GetNativePath(path);
    if (NS_FAILED(rv))
      return rv;

    const char* pathPtr = path.get();
    int status;
    if (isDirectory)
      status = rmdir(pathPtr);
    else
      status = unlink(pathPtr);

    if (status != 0)
      rv = NSRESULT_FOR_ERRNO();
  }

  return rv;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

// Only send back permissions bits: maybe we want to send back the whole
// mode_t to permit checks against other file types?
#define NORMALIZE_PERMS(mode)    ((mode)& (S_IRWXU | S_IRWXG | S_IRWXO))

NS_IMETHODIMP nsLocalFile::GetPermissions(PRUint32 *aPermissions)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  NS_ENSURE_ARG_POINTER(aPermissions);

  CHECK_mBaseRef();

  NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init];
  NSDictionary *fileAttributes = [[NSFileManager defaultManager] fileAttributesAtPath:[(NSURL*)mBaseRef path] traverseLink:YES];
  if (fileAttributes) {
    NSNumber *permissions = [fileAttributes objectForKey:NSFilePosixPermissions];
    if (permissions) {
      *aPermissions = NORMALIZE_PERMS([permissions unsignedLongValue]);
      [ap release];
      return NS_OK;
    }
  }
  [ap release];

  return NS_ERROR_FAILURE;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::SetPermissions(PRUint32 aPermissions)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  CHECK_mBaseRef();

  NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init];
  NSNumber* pNumber = [NSNumber numberWithUnsignedInt:aPermissions];
  NSDictionary* fileAttributes = [NSDictionary dictionaryWithObject:pNumber forKey:NSFilePosixPermissions];
  // changeFileAttributes:atPath: follows symbolic links though the documentation doesn't mention it
  BOOL success = [[NSFileManager defaultManager] changeFileAttributes:fileAttributes atPath:[(NSURL*)mBaseRef path]];
  [ap release];
  return (success ? NS_OK : NS_ERROR_FAILURE);

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::GetPermissionsOfLink(PRUint32 *aPermissionsOfLink)
{
    NS_ERROR("NS_ERROR_NOT_IMPLEMENTED");
    return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP nsLocalFile::SetPermissionsOfLink(PRUint32 aPermissionsOfLink)
{
    NS_ERROR("NS_ERROR_NOT_IMPLEMENTED");
    return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP nsLocalFile::GetLastModifiedTime(PRInt64 *aLastModifiedTime)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  // Check we are correctly initialized.
  CHECK_mBaseRef();

  NS_ENSURE_ARG_POINTER(aLastModifiedTime);
  
  FSRef fsRef;
  nsresult rv = GetFSRefInternal(fsRef);
  if (NS_FAILED(rv))
    return rv;
    
  FSCatalogInfo catalogInfo;
  OSErr err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoContentMod, &catalogInfo,
                                nsnull, nsnull, nsnull);
  if (err != noErr)
    return MacErrorMapper(err);
  *aLastModifiedTime = HFSPlustoNSPRTime(catalogInfo.contentModDate);  
  return NS_OK;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::SetLastModifiedTime(PRInt64 aLastModifiedTime)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  // Check we are correctly initialized.
  CHECK_mBaseRef();

  OSErr err;
  nsresult rv;
  FSRef fsRef;
  FSCatalogInfo catalogInfo;

  rv = GetFSRefInternal(fsRef);
  if (NS_FAILED(rv))
    return rv;

  FSRef parentRef;
  PRBool notifyParent;

  // Get the node flags, the content modification date and time, and the parent ref
  err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoNodeFlags + kFSCatInfoContentMod,
                           &catalogInfo, NULL, NULL, &parentRef);
  if (err != noErr)
    return MacErrorMapper(err);
  
  // Notify the parent if this is a file
  notifyParent = (0 == (catalogInfo.nodeFlags & kFSNodeIsDirectoryMask));

  NSPRtoHFSPlusTime(aLastModifiedTime, catalogInfo.contentModDate);
  err = ::FSSetCatalogInfo(&fsRef, kFSCatInfoContentMod, &catalogInfo);
  if (err != noErr)
    return MacErrorMapper(err);

  // Send a notification for the parent of the file, or for the directory
  err = FNNotify(notifyParent ? &parentRef : &fsRef, kFNDirectoryModifiedMessage, kNilOptions);
  if (err != noErr)
    return MacErrorMapper(err);

  return NS_OK;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::GetLastModifiedTimeOfLink(PRInt64 *aLastModifiedTimeOfLink)
{
    NS_ERROR("NS_ERROR_NOT_IMPLEMENTED");
    return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP nsLocalFile::SetLastModifiedTimeOfLink(PRInt64 aLastModifiedTimeOfLink)
{
    NS_ERROR("NS_ERROR_NOT_IMPLEMENTED");
    return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP nsLocalFile::GetFileSize(PRInt64 *aFileSize)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  NS_ENSURE_ARG_POINTER(aFileSize);
  *aFileSize = 0;
  
  FSRef fsRef;
  nsresult rv = GetFSRefInternal(fsRef);
  if (NS_FAILED(rv))
    return rv;
      
  FSCatalogInfo catalogInfo;
  OSErr err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoNodeFlags + kFSCatInfoDataSizes, &catalogInfo,
                                  nsnull, nsnull, nsnull);
  if (err != noErr)
    return MacErrorMapper(err);
  
  // FSGetCatalogInfo can return a bogus size for directories sometimes, so only
  // rely on the answer for files
  if ((catalogInfo.nodeFlags & kFSNodeIsDirectoryMask) == 0)
      *aFileSize = catalogInfo.dataLogicalSize;
  return NS_OK;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::SetFileSize(PRInt64 aFileSize)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  // Check we are correctly initialized.
  CHECK_mBaseRef();

  FSRef fsRef;
  nsresult rv = GetFSRefInternal(fsRef);
  if (NS_FAILED(rv))
    return rv;
  
#ifdef __LP64__
  FSIORefNum refNum;
#else
  SInt16 refNum;
#endif
  OSErr err = ::FSOpenFork(&fsRef, 0, nsnull, fsWrPerm, &refNum);
  if (err != noErr)
    return MacErrorMapper(err);
  err = ::FSSetForkSize(refNum, fsFromStart, aFileSize);
  ::FSCloseFork(refNum);  
  
  return MacErrorMapper(err);

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::GetFileSizeOfLink(PRInt64 *aFileSizeOfLink)
{
  // Check we are correctly initialized.
  CHECK_mBaseRef();

  NS_ENSURE_ARG_POINTER(aFileSizeOfLink);
  
  StFollowLinksState followLinks(*this, PR_FALSE);
  return GetFileSize(aFileSizeOfLink);
}

NS_IMETHODIMP nsLocalFile::GetTarget(nsAString& aTarget)
{
  nsCAutoString nativeString;
  nsresult rv = GetNativeTarget(nativeString);
  if (NS_FAILED(rv))
    return rv;
  CopyUTF8toUTF16NFC(nativeString, aTarget);
  return NS_OK;
}

NS_IMETHODIMP nsLocalFile::GetNativeTarget(nsACString& aNativeTarget)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  if (!mTargetRef)
    return NS_ERROR_NOT_INITIALIZED;
  nsresult rv = NS_ERROR_FAILURE;
  CFStringRef pathStrRef = ::CFURLCopyFileSystemPath(mTargetRef, kCFURLPOSIXPathStyle);
  if (pathStrRef) {
    rv = CFStringReftoUTF8(pathStrRef, aNativeTarget);
    ::CFRelease(pathStrRef);
  }
  return rv;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::GetPath(nsAString& aPath)
{
  nsCAutoString nativeString;
  nsresult rv = GetNativePath(nativeString);
  if (NS_FAILED(rv))
    return rv;
  CopyUTF8toUTF16NFC(nativeString, aPath);
  return NS_OK;
}

NS_IMETHODIMP nsLocalFile::GetNativePath(nsACString& aNativePath)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  // Check we are correctly initialized.
  CHECK_mBaseRef();

  nsresult rv = NS_ERROR_FAILURE;
  CFStringRef pathStrRef = ::CFURLCopyFileSystemPath(mBaseRef, kCFURLPOSIXPathStyle);
  if (pathStrRef) {
    rv = CFStringReftoUTF8(pathStrRef, aNativePath);
    ::CFRelease(pathStrRef);
  }
  return rv;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::Exists(PRBool *_retval)
{
  // Check we are correctly initialized.
  CHECK_mBaseRef();

  NS_ENSURE_ARG_POINTER(_retval);
  *_retval = PR_FALSE;
  
  FSRef fsRef;
  if (NS_SUCCEEDED(GetFSRefInternal(fsRef))) {
    *_retval = PR_TRUE;
  }
  
  return NS_OK;
}

NS_IMETHODIMP nsLocalFile::IsWritable(PRBool *_retval)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  CHECK_mBaseRef();

  NS_ENSURE_ARG_POINTER(_retval);
  *_retval = PR_FALSE;

  NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init];
  // don't bother resolving, this always traverses symbolic links
  *_retval = (PRBool)[[NSFileManager defaultManager] isWritableFileAtPath:[(NSURL*)mBaseRef path]];
  [ap release];

  return NS_OK;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::IsReadable(PRBool *_retval)
{
  CHECK_mBaseRef();

  NS_ENSURE_ARG_POINTER(_retval);
  *_retval = PR_FALSE;

  PRUint32 permissions;
  nsresult rv = GetPermissions(&permissions);
  if (NS_FAILED(rv))
    return rv;
  *_retval = ((permissions & S_IRUSR) != 0);
  return NS_OK;
}

NS_IMETHODIMP nsLocalFile::IsExecutable(PRBool *_retval)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  // Check we are correctly initialized.
  CHECK_mBaseRef();

  NS_ENSURE_ARG_POINTER(_retval);
  *_retval = PR_FALSE;
  
  FSRef fsRef;
  nsresult rv = GetFSRefInternal(fsRef);
  if (NS_FAILED(rv))
    return rv;
    
  LSRequestedInfo theInfoRequest = kLSRequestAllInfo;
  LSItemInfoRecord theInfo;
  if (::LSCopyItemInfoForRef(&fsRef, theInfoRequest, &theInfo) == noErr) {
    if ((theInfo.flags & kLSItemInfoIsApplication) != 0)
    *_retval = PR_TRUE;
  }
  return NS_OK;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::IsHidden(PRBool *_retval)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  NS_ENSURE_ARG_POINTER(_retval);
  *_retval = PR_FALSE;
  
  FSRef fsRef;
  nsresult rv = GetFSRefInternal(fsRef);
  if (NS_FAILED(rv))
    return rv;
  
  FSCatalogInfo catalogInfo;
  HFSUniStr255 leafName;  
  OSErr err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoFinderInfo, &catalogInfo,
                                &leafName, nsnull, nsnull);
  if (err != noErr)
    return MacErrorMapper(err);
      
  FileInfo *fInfoPtr = (FileInfo *)(catalogInfo.finderInfo); // Finder flags are in the same place whether we use FileInfo or FolderInfo
  if ((fInfoPtr->finderFlags & kIsInvisible) != 0) {
    *_retval = PR_TRUE;
  }
  else {
    // If the leaf name begins with a '.', consider it invisible
    if (leafName.length >= 1 && leafName.unicode[0] == UniChar('.'))
      *_retval = PR_TRUE;
  }
  return NS_OK;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::IsDirectory(PRBool *_retval)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  NS_ENSURE_ARG_POINTER(_retval);
  *_retval = PR_FALSE;
  
  FSRef fsRef;
  nsresult rv = GetFSRefInternal(fsRef);
  if (NS_FAILED(rv))
    return rv;
    
  FSCatalogInfo catalogInfo;
  OSErr err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoNodeFlags, &catalogInfo,
                                nsnull, nsnull, nsnull);
  if (err != noErr)
    return MacErrorMapper(err);
  *_retval = ((catalogInfo.nodeFlags & kFSNodeIsDirectoryMask) != 0);
  return NS_OK;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::IsFile(PRBool *_retval)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  NS_ENSURE_ARG_POINTER(_retval);
  *_retval = PR_FALSE;
  
  FSRef fsRef;
  nsresult rv = GetFSRefInternal(fsRef);
  if (NS_FAILED(rv))
    return rv;
    
  FSCatalogInfo catalogInfo;
  OSErr err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoNodeFlags, &catalogInfo,
                                nsnull, nsnull, nsnull);
  if (err != noErr)
    return MacErrorMapper(err);
  *_retval = ((catalogInfo.nodeFlags & kFSNodeIsDirectoryMask) == 0);
  return NS_OK;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::IsSymlink(PRBool *_retval)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  CHECK_mBaseRef();

  NS_ENSURE_ARG(_retval);
  *_retval = PR_FALSE;

  // First see if we are an actual symlink
  nsCAutoString path;
  nsresult rv = GetNativePath(path);
  if (NS_FAILED(rv))
    return rv;
  struct stat symStat;
  if (lstat(path.get(), &symStat) < 0)
    return NSRESULT_FOR_ERRNO();
  *_retval = S_ISLNK(symStat.st_mode);

  // If we're not a symlink, see if we are an old-school Mac OS alias
  if (!(*_retval)) {
    FSRef fsRef;
    if (::CFURLGetFSRef(mBaseRef, &fsRef)) {
      Boolean isAlias, isFolder;
      if (::FSIsAliasFile(&fsRef, &isAlias, &isFolder) == noErr)
        *_retval = isAlias;
    }    
  }

  return NS_OK;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::IsSpecial(PRBool *_retval)
{
    NS_ERROR("NS_ERROR_NOT_IMPLEMENTED");
    return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP nsLocalFile::Clone(nsIFile **_retval)
{
    // Just copy-construct ourselves
    *_retval = new nsLocalFile(*this);
    if (!*_retval)
      return NS_ERROR_OUT_OF_MEMORY;

    NS_ADDREF(*_retval);
    
    return NS_OK;
}

NS_IMETHODIMP nsLocalFile::Equals(nsIFile *inFile, PRBool *_retval)
{
    return EqualsInternal(inFile, _retval);
}

nsresult
nsLocalFile::EqualsInternal(nsISupports* inFile, PRBool *_retval)
{
  NS_ENSURE_ARG_POINTER(_retval);
  *_retval = PR_FALSE;
  
  nsCOMPtr<nsILocalFileMac> inMacFile(do_QueryInterface(inFile));
  if (!inFile)
    return NS_OK;
    
  nsLocalFile* inLF =
      static_cast<nsLocalFile*>((nsILocalFileMac*) inMacFile);

  // If both exist, compare FSRefs
  FSRef thisFSRef, inFSRef;
  nsresult rv1 = GetFSRefInternal(thisFSRef);
  nsresult rv2 = inLF->GetFSRefInternal(inFSRef);
  if (NS_SUCCEEDED(rv1) && NS_SUCCEEDED(rv2)) {
    *_retval = (thisFSRef == inFSRef);
    return NS_OK;
  }
  // If one exists and the other doesn't, not equal  
  if (rv1 != rv2)
    return NS_OK;
    
  // Arg, we have to get their paths and compare
  nsCAutoString thisPath, inPath;
  if (NS_FAILED(GetNativePath(thisPath)))
    return NS_ERROR_FAILURE;
  if (NS_FAILED(inMacFile->GetNativePath(inPath)))
    return NS_ERROR_FAILURE;
  *_retval = thisPath.Equals(inPath);
  
  return NS_OK;
}

NS_IMETHODIMP nsLocalFile::Contains(nsIFile *inFile, PRBool recur, PRBool *_retval)
{
  // Check we are correctly initialized.
  CHECK_mBaseRef();

  NS_ENSURE_ARG_POINTER(_retval);
  *_retval = PR_FALSE;

  PRBool isDir;
  nsresult rv = IsDirectory(&isDir);
  if (NS_FAILED(rv))
    return rv;
  if (!isDir)
    return NS_OK;     // must be a dir to contain someone

  nsCAutoString thisPath, inPath;
  if (NS_FAILED(GetNativePath(thisPath)) || NS_FAILED(inFile->GetNativePath(inPath)))
    return NS_ERROR_FAILURE;
  size_t thisPathLen = thisPath.Length();
  if ((inPath.Length() > thisPathLen + 1) && (strncasecmp(thisPath.get(), inPath.get(), thisPathLen) == 0)) {
    // Now make sure that the |inFile|'s path has a separator at thisPathLen,
    // and there's at least one more character after that.
    if (inPath[thisPathLen] == kPathSepChar)
      *_retval = PR_TRUE;
  }  
  return NS_OK;
}

NS_IMETHODIMP nsLocalFile::GetParent(nsIFile * *aParent)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  NS_ENSURE_ARG_POINTER(aParent);
  *aParent = nsnull;

  // Check we are correctly initialized.
  CHECK_mBaseRef();

  // If it can be determined without error that a file does not
  // have a parent, return nsnull for the parent and NS_OK as the result.
  // See bug 133617.
  nsresult rv = NS_OK;
  CFURLRef parentURLRef = ::CFURLCreateCopyDeletingLastPathComponent(kCFAllocatorDefault, mBaseRef);
  if (parentURLRef) {
    // If the parent path is longer than file's path then 
    // CFURLCreateCopyDeletingLastPathComponent must have simply added
    // two dots at the end - in this case indicate that there is no parent.
    // See bug 332389.
    CFStringRef path = ::CFURLGetString(mBaseRef);
    CFStringRef newPath = ::CFURLGetString(parentURLRef);
    if (::CFStringGetLength(newPath) < ::CFStringGetLength(path)) {
      rv = NS_ERROR_FAILURE;
      nsRefPtr<nsLocalFile> newFile = new nsLocalFile;
      if (newFile) {
        rv = newFile->InitWithCFURL(parentURLRef);
        if (NS_SUCCEEDED(rv)) {
          NS_ADDREF(*aParent = newFile);
          rv = NS_OK;
        }
      }
    }
    ::CFRelease(parentURLRef);
  }
  return rv;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::GetDirectoryEntries(nsISimpleEnumerator **aDirectoryEntries)
{
  NS_ENSURE_ARG_POINTER(aDirectoryEntries);
  *aDirectoryEntries = nsnull;

  nsresult rv;
  PRBool isDir;
  rv = IsDirectory(&isDir);
  if (NS_FAILED(rv)) 
    return rv;
  if (!isDir)
    return NS_ERROR_FILE_NOT_DIRECTORY;

  nsDirEnumerator* dirEnum = new nsDirEnumerator;
  if (dirEnum == nsnull)
    return NS_ERROR_OUT_OF_MEMORY;
  NS_ADDREF(dirEnum);
  rv = dirEnum->Init(this);
  if (NS_FAILED(rv)) {
    NS_RELEASE(dirEnum);
    return rv;
  }
  *aDirectoryEntries = dirEnum;
  
  return NS_OK;
}

#pragma mark -
#pragma mark [nsILocalFile]

NS_IMETHODIMP nsLocalFile::InitWithPath(const nsAString& filePath)
{
  return InitWithNativePath(NS_ConvertUTF16toUTF8(filePath));
}

NS_IMETHODIMP nsLocalFile::InitWithNativePath(const nsACString& filePath)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  nsCAutoString fixedPath;
  if (Substring(filePath, 0, 2).EqualsLiteral("~/")) {
    nsCOMPtr<nsIFile> homeDir;
    nsCAutoString homePath;
    nsresult rv = NS_GetSpecialDirectory(NS_OS_HOME_DIR,
                                        getter_AddRefs(homeDir));
    NS_ENSURE_SUCCESS(rv, rv);
    rv = homeDir->GetNativePath(homePath);
    NS_ENSURE_SUCCESS(rv, rv);
    
    fixedPath = homePath + Substring(filePath, 1, filePath.Length() - 1);
  }
  else if (filePath.IsEmpty() || filePath.First() != '/')
    return NS_ERROR_FILE_UNRECOGNIZED_PATH;
  else
    fixedPath.Assign(filePath);

  // A path with consecutive '/'s which are not between
  // nodes crashes CFURLGetFSRef(). Consecutive '/'s which
  // are between actual nodes are OK. So, convert consecutive
  // '/'s to a single one.
  fixedPath.ReplaceSubstring("//", "/");

  // On 10.2, huge paths also crash CFURLGetFSRef()
  if (fixedPath.Length() > PATH_MAX)
    return NS_ERROR_FILE_NAME_TOO_LONG;

  CFStringRef pathAsCFString;
  CFURLRef pathAsCFURL;

  pathAsCFString = ::CFStringCreateWithCString(nsnull, fixedPath.get(), kCFStringEncodingUTF8);
  if (!pathAsCFString)
    return NS_ERROR_FAILURE;
  pathAsCFURL = ::CFURLCreateWithFileSystemPath(nsnull, pathAsCFString, kCFURLPOSIXPathStyle, PR_FALSE);
  if (!pathAsCFURL) {
    ::CFRelease(pathAsCFString);
    return NS_ERROR_FAILURE;
  }
  SetBaseRef(pathAsCFURL);
  ::CFRelease(pathAsCFURL);
  ::CFRelease(pathAsCFString);
  return NS_OK;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::InitWithFile(nsILocalFile *aFile)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  NS_ENSURE_ARG(aFile);
  
  nsCOMPtr<nsILocalFileMac> aFileMac(do_QueryInterface(aFile));
  if (!aFileMac)
    return NS_ERROR_UNEXPECTED;
  CFURLRef urlRef;
  nsresult rv = aFileMac->GetCFURL(&urlRef);
  if (NS_FAILED(rv))
    return rv;
  rv = InitWithCFURL(urlRef);
  ::CFRelease(urlRef);
  return rv;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::GetFollowLinks(PRBool *aFollowLinks)
{
  NS_ENSURE_ARG_POINTER(aFollowLinks);
  
  *aFollowLinks = mFollowLinks;
  return NS_OK;
}

NS_IMETHODIMP nsLocalFile::SetFollowLinks(PRBool aFollowLinks)
{
  if (aFollowLinks != mFollowLinks) {
    mFollowLinks = aFollowLinks;
    UpdateTargetRef();
  }
  return NS_OK;
}

NS_IMETHODIMP nsLocalFile::OpenNSPRFileDesc(PRInt32 flags, PRInt32 mode, PRFileDesc **_retval)
{
  NS_ENSURE_ARG_POINTER(_retval);

  nsCAutoString path;
  nsresult rv = GetPathInternal(path);
  if (NS_FAILED(rv))
    return rv;
    
  *_retval = PR_Open(path.get(), flags, mode);
  if (! *_retval)
    return NS_ErrorAccordingToNSPR();

  return NS_OK;
}

NS_IMETHODIMP nsLocalFile::OpenANSIFileDesc(const char *mode, FILE **_retval)
{
  NS_ENSURE_ARG_POINTER(_retval);

  nsCAutoString path;
  nsresult rv = GetPathInternal(path);
  if (NS_FAILED(rv))
    return rv;
    
  *_retval = fopen(path.get(), mode);
  if (! *_retval)
    return NS_ERROR_FAILURE;

  return NS_OK;
}

NS_IMETHODIMP nsLocalFile::Load(PRLibrary **_retval)
{
  // Check we are correctly initialized.
  CHECK_mBaseRef();

  NS_ENSURE_ARG_POINTER(_retval);

  NS_TIMELINE_START_TIMER("PR_LoadLibrary");

  nsCAutoString path;
  nsresult rv = GetPathInternal(path);
  if (NS_FAILED(rv))
    return rv;

#ifdef NS_BUILD_REFCNT_LOGGING
  nsTraceRefcntImpl::SetActivityIsLegal(PR_FALSE);
#endif

  *_retval = PR_LoadLibrary(path.get());

#ifdef NS_BUILD_REFCNT_LOGGING
  nsTraceRefcntImpl::SetActivityIsLegal(PR_TRUE);
#endif

  NS_TIMELINE_STOP_TIMER("PR_LoadLibrary");
  NS_TIMELINE_MARK_TIMER1("PR_LoadLibrary", path.get());

  if (!*_retval)
    return NS_ERROR_FAILURE;
  
  return NS_OK;
}

NS_IMETHODIMP nsLocalFile::GetDiskSpaceAvailable(PRInt64 *aDiskSpaceAvailable)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  // Check we are correctly initialized.
  CHECK_mBaseRef();

  NS_ENSURE_ARG_POINTER(aDiskSpaceAvailable);
  
  FSRef fsRef;
  nsresult rv = GetFSRefInternal(fsRef);
  if (NS_FAILED(rv))
    return rv;
    
  OSErr err;
  FSCatalogInfo catalogInfo;
  err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoVolume, &catalogInfo,
                           nsnull, nsnull, nsnull);
  if (err != noErr)
    return MacErrorMapper(err);
  
  FSVolumeInfo volumeInfo;  
  err = ::FSGetVolumeInfo(catalogInfo.volume, 0, nsnull, kFSVolInfoSizes,
                          &volumeInfo, nsnull, nsnull);
  if (err != noErr)
    return MacErrorMapper(err);
    
  *aDiskSpaceAvailable = volumeInfo.freeBytes;
  return NS_OK;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::AppendRelativePath(const nsAString& relativeFilePath)
{
  return AppendRelativeNativePath(NS_ConvertUTF16toUTF8(relativeFilePath));
}

NS_IMETHODIMP nsLocalFile::AppendRelativeNativePath(const nsACString& relativeFilePath)
{  
  if (relativeFilePath.IsEmpty())
    return NS_OK;
  // No leading '/' 
  if (relativeFilePath.First() == '/')
    return NS_ERROR_FILE_UNRECOGNIZED_PATH;

  // Parse the nodes and call Append() for each
  nsACString::const_iterator nodeBegin, pathEnd;
  relativeFilePath.BeginReading(nodeBegin);
  relativeFilePath.EndReading(pathEnd);
  nsACString::const_iterator nodeEnd(nodeBegin);
  
  while (nodeEnd != pathEnd) {
    FindCharInReadable(kPathSepChar, nodeEnd, pathEnd);
    nsresult rv = AppendNative(Substring(nodeBegin, nodeEnd));
    if (NS_FAILED(rv))
      return rv;
    if (nodeEnd != pathEnd) // If there's more left in the string, inc over the '/' nodeEnd is on.
      ++nodeEnd;
    nodeBegin = nodeEnd;
  }
  return NS_OK;
}

NS_IMETHODIMP nsLocalFile::GetPersistentDescriptor(nsACString& aPersistentDescriptor)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  FSRef fsRef;
  nsresult rv = GetFSRefInternal(fsRef);
  if (NS_FAILED(rv))
    return rv;
    
  AliasHandle aliasH;
  OSErr err = ::FSNewAlias(nsnull, &fsRef, &aliasH);
  if (err != noErr)
    return MacErrorMapper(err);
    
   PRUint32 bytes = ::GetHandleSize((Handle) aliasH);
   ::HLock((Handle) aliasH);
   // Passing nsnull for dest makes NULL-term string
   char* buf = PL_Base64Encode((const char*)*aliasH, bytes, nsnull);
   ::DisposeHandle((Handle) aliasH);
   NS_ENSURE_TRUE(buf, NS_ERROR_OUT_OF_MEMORY);
   
   aPersistentDescriptor = buf;
   PR_Free(buf);

  return NS_OK;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::SetPersistentDescriptor(const nsACString& aPersistentDescriptor)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  if (aPersistentDescriptor.IsEmpty())
    return NS_ERROR_INVALID_ARG;

  // Support pathnames as user-supplied descriptors if they begin with '/'
  // or '~'.  These characters do not collide with the base64 set used for
  // encoding alias records.
  char first = aPersistentDescriptor.First();
  if (first == '/' || first == '~')
    return InitWithNativePath(aPersistentDescriptor);

  nsresult rv = NS_OK;
  
  PRUint32 dataSize = aPersistentDescriptor.Length();    
  char* decodedData = PL_Base64Decode(PromiseFlatCString(aPersistentDescriptor).get(), dataSize, nsnull);
  if (!decodedData) {
    NS_ERROR("SetPersistentDescriptor was given bad data");
    return NS_ERROR_FAILURE;
  }
  
  // Cast to an alias record and resolve.
  AliasRecord aliasHeader = *(AliasPtr)decodedData;
  PRInt32 aliasSize = ::GetAliasSizeFromPtr(&aliasHeader);
  if (aliasSize > ((PRInt32)dataSize * 3) / 4) { // be paranoid about having too few data
    PR_Free(decodedData);
    return NS_ERROR_FAILURE;
  }
  
  // Move the now-decoded data into the Handle.
  // The size of the decoded data is 3/4 the size of the encoded data. See plbase64.h
  Handle  newHandle = nsnull;
  if (::PtrToHand(decodedData, &newHandle, aliasSize) != noErr)
    rv = NS_ERROR_OUT_OF_MEMORY;
  PR_Free(decodedData);
  if (NS_FAILED(rv))
    return rv;

  Boolean changed;
  FSRef resolvedFSRef;
  OSErr err = ::FSResolveAlias(nsnull, (AliasHandle)newHandle, &resolvedFSRef, &changed);
    
  rv = MacErrorMapper(err);
  DisposeHandle(newHandle);
  if (NS_FAILED(rv))
    return rv;
 
  return InitWithFSRef(&resolvedFSRef);

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::Reveal()
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  BOOL success = NO;

  CFURLRef urlRef;
  if (NS_SUCCEEDED(GetCFURL(&urlRef)) && urlRef) {
    NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init];
    success = [[NSWorkspace sharedWorkspace] selectFile:[(NSURL*)urlRef path] inFileViewerRootedAtPath:@""];
    [ap release];

    ::CFRelease(urlRef);
  }

  return (success ? NS_OK : NS_ERROR_FAILURE);

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::Launch()
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  BOOL success = NO;

  CFURLRef urlRef;
  if (NS_SUCCEEDED(GetCFURL(&urlRef)) && urlRef) {
    NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init];
    success = [[NSWorkspace sharedWorkspace] openURL:(NSURL*)urlRef];
    [ap release];

    ::CFRelease(urlRef);
  }

  return (success ? NS_OK : NS_ERROR_FAILURE);

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

#pragma mark -
#pragma mark [nsILocalFileMac]

NS_IMETHODIMP nsLocalFile::InitWithCFURL(CFURLRef aCFURL)
{
  NS_ENSURE_ARG(aCFURL);
  
  SetBaseRef(aCFURL);
  return NS_OK;
}

NS_IMETHODIMP nsLocalFile::InitWithFSRef(const FSRef *aFSRef)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  NS_ENSURE_ARG(aFSRef);
  nsresult rv = NS_ERROR_FAILURE;
  
  CFURLRef newURLRef = ::CFURLCreateFromFSRef(kCFAllocatorDefault, aFSRef);
  if (newURLRef) {
    SetBaseRef(newURLRef);
    ::CFRelease(newURLRef);
    rv = NS_OK;
  }
  return rv;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::InitToAppWithCreatorCode(OSType aAppCreator)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  FSRef fsRef;
  OSErr err = ::LSFindApplicationForInfo(aAppCreator, nsnull, nsnull, &fsRef, nsnull);
  if (err != noErr)
    return MacErrorMapper(err);
  return InitWithFSRef(&fsRef);

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::GetCFURL(CFURLRef *_retval)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  NS_ENSURE_ARG_POINTER(_retval);
  CFURLRef whichURLRef = mFollowLinks ? mTargetRef : mBaseRef;
  if (whichURLRef)
    ::CFRetain(whichURLRef);
  *_retval = whichURLRef;
  return whichURLRef ? NS_OK : NS_ERROR_FAILURE;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::GetFSRef(FSRef *_retval)
{
  NS_ENSURE_ARG_POINTER(_retval);
  return GetFSRefInternal(*_retval);
}

NS_IMETHODIMP nsLocalFile::GetFSSpec(FSSpec *_retval)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  NS_ENSURE_ARG_POINTER(_retval);
  CHECK_mBaseRef();

  FSRef fsRef;
  nsresult rv = GetFSRefInternal(fsRef);
  if (NS_SUCCEEDED(rv)) {
    OSErr err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoNone, nsnull, nsnull, _retval, nsnull);
    return MacErrorMapper(err); 
  }
  return rv;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::GetFileSizeWithResFork(PRInt64 *aFileSizeWithResFork)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  NS_ENSURE_ARG_POINTER(aFileSizeWithResFork);
  
  FSRef fsRef;
  nsresult rv = GetFSRefInternal(fsRef);
  if (NS_FAILED(rv))
    return rv;
      
  FSCatalogInfo catalogInfo;
  OSErr err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoDataSizes + kFSCatInfoRsrcSizes,
                                 &catalogInfo, nsnull, nsnull, nsnull);
  if (err != noErr)
    return MacErrorMapper(err);
    
  *aFileSizeWithResFork = catalogInfo.dataLogicalSize + catalogInfo.rsrcLogicalSize;
  return NS_OK;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::GetFileType(OSType *aFileType)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  NS_ENSURE_ARG_POINTER(aFileType);

  CHECK_mBaseRef();
  
  nsresult rv = NS_ERROR_FAILURE;

  NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init];
  NSDictionary* dict = [[NSFileManager defaultManager] fileAttributesAtPath:[(NSURL*)mBaseRef path] traverseLink:YES];
  NSNumber* typeNum = (NSNumber*)[dict objectForKey:NSFileHFSTypeCode];
  if (typeNum) {
    *aFileType = [typeNum unsignedLongValue];
    rv = NS_OK;
  }
  [ap release];

  return rv;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::SetFileType(OSType aFileType)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  CHECK_mBaseRef();

  NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init];
  NSDictionary* dict = [NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedLong:aFileType] forKey:NSFileHFSTypeCode];
  BOOL success = [[NSFileManager defaultManager] changeFileAttributes:dict atPath:[(NSURL*)mBaseRef path]];
  [ap release];
  return (success ? NS_OK : NS_ERROR_FAILURE);

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::GetFileCreator(OSType *aFileCreator)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  NS_ENSURE_ARG_POINTER(aFileCreator);

  CHECK_mBaseRef();

  nsresult rv = NS_ERROR_FAILURE;

  NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init];
  NSDictionary* dict = [[NSFileManager defaultManager] fileAttributesAtPath:[(NSURL*)mBaseRef path] traverseLink:YES];
  id creatorNum = (NSNumber*)[dict objectForKey:NSFileHFSCreatorCode];
  if (creatorNum) {
    *aFileCreator = [creatorNum unsignedLongValue];
    rv = NS_OK;
  }
  [ap release];

  return rv;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::SetFileCreator(OSType aFileCreator)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  CHECK_mBaseRef();

  NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init];
  NSDictionary* dict = [NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedLong:aFileCreator] forKey:NSFileHFSCreatorCode];
  BOOL success = [[NSFileManager defaultManager] changeFileAttributes:dict atPath:[(NSURL*)mBaseRef path]];
  [ap release];
  return (success ? NS_OK : NS_ERROR_FAILURE);

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::SetFileTypeAndCreatorFromMIMEType(const char *aMIMEType)
{
  // XXX - This should be cut from the API. Would create an evil dependency.
  NS_ERROR("NS_ERROR_NOT_IMPLEMENTED");
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP nsLocalFile::SetFileTypeAndCreatorFromExtension(const char *aExtension)
{
  // XXX - This should be cut from the API. Would create an evil dependency.
  NS_ERROR("NS_ERROR_NOT_IMPLEMENTED");
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP nsLocalFile::LaunchWithDoc(nsILocalFile *aDocToLoad, PRBool aLaunchInBackground)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  PRBool isExecutable;
  nsresult rv = IsExecutable(&isExecutable);
  if (NS_FAILED(rv))
    return rv;
  if (!isExecutable)
    return NS_ERROR_FILE_EXECUTION_FAILED;

  FSRef appFSRef, docFSRef;
  rv = GetFSRefInternal(appFSRef);
  if (NS_FAILED(rv))
    return rv;

  if (aDocToLoad) {
    nsCOMPtr<nsILocalFileMac> macDoc = do_QueryInterface(aDocToLoad);
    rv = macDoc->GetFSRef(&docFSRef);
    if (NS_FAILED(rv))
      return rv;
  }
  
  LSLaunchFlags       theLaunchFlags = kLSLaunchDefaults;
  LSLaunchFSRefSpec   thelaunchSpec;

  if (aLaunchInBackground)
    theLaunchFlags |= kLSLaunchDontSwitch;
  memset(&thelaunchSpec, 0, sizeof(LSLaunchFSRefSpec));

  thelaunchSpec.appRef = &appFSRef;
  if (aDocToLoad) {
    thelaunchSpec.numDocs = 1;
    thelaunchSpec.itemRefs = &docFSRef;
  }
  thelaunchSpec.launchFlags = theLaunchFlags;

  OSErr err = ::LSOpenFromRefSpec(&thelaunchSpec, NULL);
  if (err != noErr)
    return MacErrorMapper(err);

  return NS_OK;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::OpenDocWithApp(nsILocalFile *aAppToOpenWith, PRBool aLaunchInBackground)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  nsresult rv;
  OSErr err;

  FSRef docFSRef, appFSRef;
  rv = GetFSRefInternal(docFSRef);
  if (NS_FAILED(rv))
    return rv;

  if (aAppToOpenWith) {
    nsCOMPtr<nsILocalFileMac> appFileMac = do_QueryInterface(aAppToOpenWith, &rv);
    if (!appFileMac)
      return rv;

    PRBool isExecutable;
    rv = appFileMac->IsExecutable(&isExecutable);
    if (NS_FAILED(rv))
      return rv;
    if (!isExecutable)
      return NS_ERROR_FILE_EXECUTION_FAILED;
    
    rv = appFileMac->GetFSRef(&appFSRef);
    if (NS_FAILED(rv))
      return rv;
  }
  else {
    OSType  fileCreator;
    rv = GetFileCreator(&fileCreator);
    if (NS_FAILED(rv))
      return rv;

    err = ::LSFindApplicationForInfo(fileCreator, nsnull, nsnull, &appFSRef, nsnull);
    if (err != noErr)
      return MacErrorMapper(err);
  }
  
  LSLaunchFlags       theLaunchFlags = kLSLaunchDefaults;
  LSLaunchFSRefSpec   thelaunchSpec;

  if (aLaunchInBackground)
  theLaunchFlags |= kLSLaunchDontSwitch;
  memset(&thelaunchSpec, 0, sizeof(LSLaunchFSRefSpec));

  thelaunchSpec.appRef = &appFSRef;
  thelaunchSpec.numDocs = 1;
  thelaunchSpec.itemRefs = &docFSRef;
  thelaunchSpec.launchFlags = theLaunchFlags;

  err = ::LSOpenFromRefSpec(&thelaunchSpec, NULL);
  if (err != noErr)
    return MacErrorMapper(err);

  return NS_OK;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP nsLocalFile::IsPackage(PRBool *_retval)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  NS_ENSURE_ARG(_retval);
  *_retval = PR_FALSE;
  
  CFURLRef urlRef;
  if (NS_SUCCEEDED(GetCFURL(&urlRef)) && urlRef) {
    *_retval = [[NSWorkspace sharedWorkspace] isFilePackageAtPath:[(NSURL *)urlRef path]];
    ::CFRelease(urlRef);
    
    return NS_OK;
  }
  
  return NS_ERROR_FAILURE;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP
nsLocalFile::GetBundleDisplayName(nsAString& outBundleName)
{
  PRBool isPackage = PR_FALSE;
  nsresult rv = IsPackage(&isPackage);
  if (NS_FAILED(rv) || !isPackage)
    return NS_ERROR_FAILURE;
  
  nsAutoString name;
  rv = GetLeafName(name);
  if (NS_FAILED(rv))
    return rv;
  
  PRInt32 length = name.Length();
  if (Substring(name, length - 4, length).EqualsLiteral(".app")) {
    // 4 characters in ".app"
    outBundleName = Substring(name, 0, length - 4);
  }
  else
    outBundleName = name;
  
  return NS_OK;
}

NS_IMETHODIMP
nsLocalFile::GetBundleIdentifier(nsACString& outBundleIdentifier)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  nsresult rv = NS_ERROR_FAILURE;

  CFURLRef urlRef;
  if (NS_SUCCEEDED(GetCFURL(&urlRef))) {
    CFBundleRef bundle = ::CFBundleCreate(NULL, urlRef);
    if (bundle) {
      CFStringRef bundleIdentifier = ::CFBundleGetIdentifier(bundle);
      if (bundleIdentifier)
        rv = CFStringReftoUTF8(bundleIdentifier, outBundleIdentifier);

      ::CFRelease(bundle);
    }
    ::CFRelease(urlRef);
  }

  return rv;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

#pragma mark -
#pragma mark [Protected Methods]

nsresult nsLocalFile::SetBaseRef(CFURLRef aCFURLRef)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  NS_ENSURE_ARG(aCFURLRef);
  
  ::CFRetain(aCFURLRef);
  if (mBaseRef)
    ::CFRelease(mBaseRef);
  mBaseRef = aCFURLRef;

  mFollowLinksDirty = PR_TRUE;  
  UpdateTargetRef();
  return NS_OK;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

nsresult nsLocalFile::UpdateTargetRef()
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  // Check we are correctly initialized.
  CHECK_mBaseRef();
  
  if (mFollowLinksDirty) {
    if (mTargetRef) {
      ::CFRelease(mTargetRef);
      mTargetRef = nsnull;
    }
    if (mFollowLinks) {
      mTargetRef = mBaseRef;
      ::CFRetain(mTargetRef);

      FSRef fsRef;
      if (::CFURLGetFSRef(mBaseRef, &fsRef)) {
        Boolean targetIsFolder, wasAliased;
        if (FSResolveAliasFile(&fsRef, true /*resolveAliasChains*/, 
            &targetIsFolder, &wasAliased) == noErr && wasAliased) {
          ::CFRelease(mTargetRef);
          mTargetRef = CFURLCreateFromFSRef(NULL, &fsRef);
          if (!mTargetRef)
            return NS_ERROR_FAILURE;
        }
      }
      mFollowLinksDirty = PR_FALSE;
    }
  }
  return NS_OK;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

nsresult nsLocalFile::GetFSRefInternal(FSRef& aFSRef)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  CFURLRef whichURLRef = mFollowLinks ? mTargetRef : mBaseRef;
  NS_ENSURE_TRUE(whichURLRef, NS_ERROR_NULL_POINTER);
  if (::CFURLGetFSRef(whichURLRef, &aFSRef))
    return NS_OK;

  // See if we are a symlink, and construct an FSRef for that
  nsCAutoString path;
  nsresult rv = GetNativePath(path); // XXXjag inline and use whichURLRef?
  if (NS_FAILED(rv))
    return rv;

  struct stat symStat;
  if (lstat(path.get(), &symStat) < 0)
    return NSRESULT_FOR_ERRNO();

  rv = NS_ERROR_FILE_NOT_FOUND; // best guess if all else fails

  if (S_ISLNK(symStat.st_mode)) {
    CFURLRef parentURLRef = ::CFURLCreateCopyDeletingLastPathComponent(kCFAllocatorDefault, whichURLRef);
    if (parentURLRef) {
      FSRef parentFSRef;
      if (::CFURLGetFSRef(parentURLRef, &parentFSRef)) {
        CFStringRef leafRef = ::CFURLCopyLastPathComponent(whichURLRef);
        if (leafRef) {
          CFRange range = { 0, ::CFStringGetLength(leafRef) };
          if (range.length > 0) {
            UniChar* leaf = new UniChar[range.length];
            if (leaf) {
              ::CFStringGetCharacters(leafRef, range, leaf);
              if (::FSMakeFSRefUnicode(&parentFSRef, range.length, leaf,
                                       kTextEncodingUnknown, &aFSRef) == noErr)
                rv = NS_OK;

              delete[] leaf;
            }
          }
          ::CFRelease(leafRef);
        }
      }
      ::CFRelease(parentURLRef);
    }
  }

  return rv;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

nsresult nsLocalFile::GetPathInternal(nsACString& path)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  nsresult rv = NS_ERROR_FAILURE;
  
  CFURLRef whichURLRef = mFollowLinks ? mTargetRef : mBaseRef;
  NS_ENSURE_TRUE(whichURLRef, NS_ERROR_NULL_POINTER);
   
  CFStringRef pathStrRef = ::CFURLCopyFileSystemPath(whichURLRef, kCFURLPOSIXPathStyle);
  if (pathStrRef) {
    rv = CFStringReftoUTF8(pathStrRef, path);
    ::CFRelease(pathStrRef);
  }
  return rv;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

nsresult nsLocalFile::CopyInternal(nsIFile* aParentDir,
                                   const nsAString& newName,
                                   PRBool followLinks)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  // Check we are correctly initialized.
  CHECK_mBaseRef();

  StFollowLinksState srcFollowState(*this, followLinks);

  nsresult rv;
  OSErr err;
  FSRef srcFSRef;

  rv = GetFSRefInternal(srcFSRef);
  if (NS_FAILED(rv))
    return rv;

  nsCOMPtr<nsIFile> newParentDir = aParentDir;

  if (!newParentDir) {
    if (newName.IsEmpty())
      return NS_ERROR_INVALID_ARG;
    rv = GetParent(getter_AddRefs(newParentDir));
    if (NS_FAILED(rv))
      return rv;    
  }

  // If newParentDir does not exist, create it
  PRBool exists;
  rv = newParentDir->Exists(&exists);
  if (NS_FAILED(rv))
    return rv;
  if (!exists) {
    rv = newParentDir->Create(nsIFile::DIRECTORY_TYPE, 0777);
    if (NS_FAILED(rv))
      return rv;
  }

  FSRef destFSRef;
  nsCOMPtr<nsILocalFileMac> newParentDirMac(do_QueryInterface(newParentDir));
  if (!newParentDirMac)
    return NS_ERROR_NO_INTERFACE;
  rv = newParentDirMac->GetFSRef(&destFSRef);
  if (NS_FAILED(rv))
    return rv;

  CFStringRef destNameStr = NULL;
  if (newName.Length() > 0) {
    destNameStr = ::CFStringCreateWithCharacters(kCFAllocatorDefault,
                                                 PromiseFlatString(newName).get(),
                                                 newName.Length());
  }
  err = ::FSCopyObjectSync(&srcFSRef, &destFSRef, destNameStr, NULL, kFSFileOperationDefaultOptions);
  if (destNameStr)
    ::CFRelease(destNameStr);

  return MacErrorMapper(err);

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

const PRInt64 kMillisecsPerSec = 1000LL;
const PRInt64 kUTCDateTimeFractionDivisor = 65535LL;

PRInt64 nsLocalFile::HFSPlustoNSPRTime(const UTCDateTime& utcTime)
{
  // Start with seconds since Jan. 1, 1904 GMT
  PRInt64 result = ((PRInt64)utcTime.highSeconds << 32) + (PRInt64)utcTime.lowSeconds; 
  // Subtract to convert to NSPR epoch of 1970
  result -= kJanuaryFirst1970Seconds;
  // Convert to millisecs
  result *= kMillisecsPerSec;
  // Convert the fraction to millisecs and add it
  result += ((PRInt64)utcTime.fraction * kMillisecsPerSec) / kUTCDateTimeFractionDivisor;

  return result;
}

void nsLocalFile::NSPRtoHFSPlusTime(PRInt64 nsprTime, UTCDateTime& utcTime)
{
  PRInt64 fraction = nsprTime % kMillisecsPerSec;
  PRInt64 seconds = (nsprTime / kMillisecsPerSec) + kJanuaryFirst1970Seconds;
  utcTime.highSeconds = (UInt16)((PRUint64)seconds >> 32);
  utcTime.lowSeconds = (UInt32)seconds;
  utcTime.fraction = (UInt16)((fraction * kUTCDateTimeFractionDivisor) / kMillisecsPerSec);
}

nsresult nsLocalFile::CFStringReftoUTF8(CFStringRef aInStrRef, nsACString& aOutStr)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

  nsresult rv = NS_ERROR_FAILURE;
  CFIndex usedBufLen, inStrLen = ::CFStringGetLength(aInStrRef);
  CFIndex charsConverted = ::CFStringGetBytes(aInStrRef, CFRangeMake(0, inStrLen),
                              kCFStringEncodingUTF8, 0, PR_FALSE, nsnull, 0, &usedBufLen);
  if (charsConverted == inStrLen) {
    aOutStr.SetLength(usedBufLen);
    if (aOutStr.Length() != (unsigned int)usedBufLen)
      return NS_ERROR_OUT_OF_MEMORY;
    UInt8 *buffer = (UInt8*) aOutStr.BeginWriting();

    ::CFStringGetBytes(aInStrRef, CFRangeMake(0, inStrLen),
                       kCFStringEncodingUTF8, 0, false, buffer, usedBufLen, &usedBufLen);
    rv = NS_OK;
  }
  return rv;

  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

// nsIHashable

NS_IMETHODIMP
nsLocalFile::Equals(nsIHashable* aOther, PRBool *aResult)
{
    return EqualsInternal(aOther, aResult);
}

NS_IMETHODIMP
nsLocalFile::GetHashCode(PRUint32 *aResult)
{
    NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;

    CFStringRef pathStrRef = ::CFURLCopyFileSystemPath(mBaseRef, kCFURLPOSIXPathStyle);
    nsCAutoString path;
    CFStringReftoUTF8(pathStrRef, path);
    ::CFRelease(pathStrRef);
    *aResult = HashString(path);
    return NS_OK;

    NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

#pragma mark -
#pragma mark [Global Functions]

void nsLocalFile::GlobalInit()
{
}

void nsLocalFile::GlobalShutdown()
{
}

nsresult NS_NewLocalFile(const nsAString& path, PRBool followLinks, nsILocalFile* *result)
{
    nsLocalFile* file = new nsLocalFile;
    if (file == nsnull)
        return NS_ERROR_OUT_OF_MEMORY;
    NS_ADDREF(file);

    file->SetFollowLinks(followLinks);

    if (!path.IsEmpty()) {
        nsresult rv = file->InitWithPath(path);
        if (NS_FAILED(rv)) {
            NS_RELEASE(file);
            return rv;
        }
    }
    *result = file;
    return NS_OK;
}

nsresult NS_NewNativeLocalFile(const nsACString& path, PRBool followLinks, nsILocalFile **result)
{
    return NS_NewLocalFile(NS_ConvertUTF8toUTF16(path), followLinks, result);
}

nsresult NS_NewLocalFileWithFSRef(const FSRef* aFSRef, PRBool aFollowLinks, nsILocalFileMac** result)
{
    nsLocalFile* file = new nsLocalFile();
    if (file == nsnull)
        return NS_ERROR_OUT_OF_MEMORY;
    NS_ADDREF(file);

    file->SetFollowLinks(aFollowLinks);

    nsresult rv = file->InitWithFSRef(aFSRef);
    if (NS_FAILED(rv)) {
        NS_RELEASE(file);
        return rv;
    }
    *result = file;
    return NS_OK;
}

#pragma mark -
#pragma mark [Static Functions]

static nsresult MacErrorMapper(OSErr inErr)
{
    nsresult outErr;
    
    switch (inErr)
    {
        case noErr:
            outErr = NS_OK;
            break;

        case fnfErr:
        case afpObjectNotFound:
        case afpDirNotFound:
            outErr = NS_ERROR_FILE_NOT_FOUND;
            break;

        case dupFNErr:
        case afpObjectExists:
            outErr = NS_ERROR_FILE_ALREADY_EXISTS;
            break;
        
        case dskFulErr:
        case afpDiskFull:
            outErr = NS_ERROR_FILE_DISK_FULL;
            break;
        
        case fLckdErr:
        case afpVolLocked:
            outErr = NS_ERROR_FILE_IS_LOCKED;
            break;
        
        case afpAccessDenied:
            outErr = NS_ERROR_FILE_ACCESS_DENIED;
            break;

        case afpDirNotEmpty:
            outErr = NS_ERROR_FILE_DIR_NOT_EMPTY;
            break;
            
        // Can't find good map for some
        case bdNamErr:
            outErr = NS_ERROR_FAILURE;
            break;

        default:    
            outErr = NS_ERROR_FAILURE;
            break;
    }
    return outErr;
}

// Convert a UTF-8 string to a UTF-16 string while normalizing to
// Normalization Form C (composed Unicode). We need this because
// Mac OS X file system uses NFD (Normalization Form D : decomposed Unicode)
// while most other OS', server-side programs usually expect NFC.

typedef void (*UnicodeNormalizer) (CFMutableStringRef, CFStringNormalizationForm);
static void CopyUTF8toUTF16NFC(const nsACString& aSrc, nsAString& aResult)
{
    NS_OBJC_BEGIN_TRY_ABORT_BLOCK;

    static PRBool sChecked = PR_FALSE;
    static UnicodeNormalizer sUnicodeNormalizer = NULL;

    // CFStringNormalize was not introduced until Mac OS 10.2
    if (!sChecked) {
        CFBundleRef carbonBundle =
            CFBundleGetBundleWithIdentifier(CFSTR("com.apple.Carbon"));
        if (carbonBundle)
            sUnicodeNormalizer = (UnicodeNormalizer)
                ::CFBundleGetFunctionPointerForName(carbonBundle,
                                                    CFSTR("CFStringNormalize"));
        sChecked = PR_TRUE;
    }

    if (!sUnicodeNormalizer) {  // OS X 10.2 or earlier
        CopyUTF8toUTF16(aSrc, aResult);
        return;  
    }

    const nsAFlatCString &inFlatSrc = PromiseFlatCString(aSrc);

    // The number of 16bit code units in a UTF-16 string will never be
    // larger than the number of bytes in the corresponding UTF-8 string.
    CFMutableStringRef inStr =
        ::CFStringCreateMutable(NULL, inFlatSrc.Length());

    if (!inStr) {
        CopyUTF8toUTF16(aSrc, aResult);
        return;  
    }
     
    ::CFStringAppendCString(inStr, inFlatSrc.get(), kCFStringEncodingUTF8); 

    sUnicodeNormalizer(inStr, kCFStringNormalizationFormC);

    CFIndex length = CFStringGetLength(inStr);
    const UniChar* chars = CFStringGetCharactersPtr(inStr);

    if (chars) 
        aResult.Assign(chars, length);
    else {
        nsAutoTArray<UniChar, FILENAME_BUFFER_SIZE> buffer;
        if (!buffer.SetLength(length))
            CopyUTF8toUTF16(aSrc, aResult);
        else {
            CFStringGetCharacters(inStr, CFRangeMake(0, length), buffer.Elements());
            aResult.Assign(buffer.Elements(), length);
        }
    }
    CFRelease(inStr);

    NS_OBJC_END_TRY_ABORT_BLOCK;
}
