/* -*- 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>
 *
 * 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 "nsString.h"
#include "nsReadableUtils.h"
#include "nsIDirectoryEnumerator.h"
#include "nsISimpleEnumerator.h"
#include "nsITimelineService.h"
#include "nsVoidArray.h"

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

#include "MoreFilesX.h"
#include "FSCopyObject.h"
#include "nsAutoBuffer.h"
#include "nsTraceRefcntImpl.h"

// Mac Includes
#include <Carbon/Carbon.h>

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

#if !defined(MAC_OS_X_VERSION_10_4) || MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_4
#define GetAliasSizeFromRecord(aliasRecord) aliasRecord.aliasSize
#else
#define GetAliasSizeFromRecord(aliasRecord) GetAliasSizeFromPtr(&aliasRecord)
#endif

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

//*****************************************************************************
//  Static Function Prototypes
//*****************************************************************************

static nsresult MacErrorMapper(OSErr inErr);
static OSErr FindRunningAppBySignature(OSType aAppSig, ProcessSerialNumber& outPsn);
static void CopyUTF8toUTF16NFC(const nsACString& aSrc, nsAString& aResult);

//*****************************************************************************
//  Local Helper Classes
//*****************************************************************************

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

bool operator==(const FSRef& lhs, const FSRef& rhs)
{
  return (::FSCompareFSRefs(&lhs, &rhs) == noErr);
}

#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_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_IMETHOD HasMoreElements(PRBool *result)
        {
          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) {
              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_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()
        {
          if (mIterator) {
            ::FSCloseIterator(mIterator);
            mIterator = nsnull;
          }
          if (mFSRefsArray) {
            nsMemory::Free(mFSRefsArray);
            mFSRefsArray = nsnull;
          }
          return NS_OK;
        }

    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)

#pragma mark -
#pragma mark [StAEDesc]

class StAEDesc: public AEDesc
{
public:
    StAEDesc()
    {
      descriptorType = typeNull;
      dataHandle = nil;
    }

    ~StAEDesc()
    {
      ::AEDisposeDesc(this);
    }
};

#define FILENAME_BUFFER_SIZE 512

//*****************************************************************************
//  nsLocalFile
//*****************************************************************************

const char      nsLocalFile::kPathSepChar = '/';
const PRUnichar nsLocalFile::kPathSepUnichar = '/';

// 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   nsLocalFile::kJanuaryFirst1970Seconds = 2082844800LL;

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

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

nsLocalFile::nsLocalFile(const nsLocalFile& src) :
  mBaseRef(src.mBaseRef),
  mTargetRef(src.mTargetRef),
  mCachedFSRef(src.mCachedFSRef),
  mCachedFSRefValid(src.mCachedFSRefValid),
  mFollowLinks(src.mFollowLinks),
  mFollowLinksDirty(src.mFollowLinksDirty)
{
  // A CFURLRef is immutable so no need to copy, just retain.
  if (mBaseRef)
    ::CFRetain(mBaseRef);
  if (mTargetRef)
    ::CFRetain(mTargetRef);
}

nsLocalFile::~nsLocalFile()
{
  if (mBaseRef)
    ::CFRelease(mBaseRef);
  if (mTargetRef)
    ::CFRelease(mTargetRef);
}


//*****************************************************************************
//  nsLocalFile::nsISupports
//*****************************************************************************
#pragma mark -
#pragma mark [nsISupports]

NS_IMPL_THREADSAFE_ISUPPORTS4(nsLocalFile,
                              nsILocalFileMac,
                              nsILocalFile,
                              nsIFile,
                              nsIHashable)

NS_METHOD 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;
}


//*****************************************************************************
//  nsLocalFile::nsIFile
//*****************************************************************************
#pragma mark -
#pragma mark [nsIFile]

/* void append (in AString node); */
NS_IMETHODIMP nsLocalFile::Append(const nsAString& aNode)
{
  return AppendNative(NS_ConvertUTF16toUTF8(aNode));
}

/* [noscript] void appendNative (in ACString node); */
NS_IMETHODIMP nsLocalFile::AppendNative(const nsACString& aNode)
{
  // 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;
}

/* void normalize (); */
NS_IMETHODIMP nsLocalFile::Normalize()
{
  // 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;
}

/* void create (in unsigned long type, in unsigned long permissions); */
NS_IMETHODIMP nsLocalFile::Create(PRUint32 type, PRUint32 permissions)
{
  if (type != NORMAL_FILE_TYPE && type != DIRECTORY_TYPE)
    return NS_ERROR_FILE_UNKNOWN_TYPE;

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

  nsStringArray nonExtantNodes;
  CFURLRef pathURLRef = mBaseRef;
  FSRef pathFSRef;
  CFStringRef leafStrRef = nsnull;
  nsAutoBuffer<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.EnsureElemCapacity(leafLen + 1))
      break;
    ::CFStringGetCharacters(leafStrRef, CFRangeMake(0, leafLen), buffer.get());
    buffer.get()[leafLen] = '\0';
    nonExtantNodes.AppendString(nsString(nsDependentString(buffer.get())));
    ::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 = nonExtantNodes.Count();
  if (nodesToCreate == 0)
    return NS_ERROR_FILE_ALREADY_EXISTS;

  OSErr err;
  nsAutoString nextNodeName;
  for (PRInt32 i = nodesToCreate - 1; i > 0; i--) {
    nonExtantNodes.StringAt(i, nextNodeName);
    err = ::FSCreateDirectoryUnicode(&pathFSRef,
                                      nextNodeName.Length(),
                                      (const UniChar *)nextNodeName.get(),
                                      kFSCatInfoNone,
                                      nsnull, &pathFSRef, nsnull, nsnull);
    if (err != noErr)
      return MacErrorMapper(err);
  }
  nonExtantNodes.StringAt(0, nextNodeName);
  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);
}

/* attribute AString leafName; */
NS_IMETHODIMP nsLocalFile::GetLeafName(nsAString& aLeafName)
{
  nsCAutoString nativeString;
  nsresult rv = GetNativeLeafName(nativeString);
  if (NS_FAILED(rv))
    return rv;
  CopyUTF8toUTF16NFC(nativeString, aLeafName);
  return NS_OK;
}

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

/* [noscript] attribute ACString nativeLeafName; */
NS_IMETHODIMP nsLocalFile::GetNativeLeafName(nsACString& aNativeLeafName)
{
  // 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_IMETHODIMP nsLocalFile::SetNativeLeafName(const nsACString& aNativeLeafName)
{
  // 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;
}

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

/* [noscrpit] void CopyToNative (in nsIFile newParentDir, in ACString newName); */
NS_IMETHODIMP nsLocalFile::CopyToNative(nsIFile *newParentDir, const nsACString& newName)
{
  return CopyInternal(newParentDir, NS_ConvertUTF8toUTF16(newName), PR_FALSE);
}

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

/* [noscript] void copyToFollowingLinksNative (in nsIFile newParentDir, in ACString newName); */
NS_IMETHODIMP nsLocalFile::CopyToFollowingLinksNative(nsIFile *newParentDir, const nsACString& newName)
{
  return CopyInternal(newParentDir, NS_ConvertUTF8toUTF16(newName), PR_TRUE);
}

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

/* [noscript] void moveToNative (in nsIFile newParentDir, in ACString newName); */
NS_IMETHODIMP nsLocalFile::MoveToNative(nsIFile *newParentDir, const nsACString& newName)
{
  // 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;
}

/* void remove (in boolean recursive); */
NS_IMETHODIMP nsLocalFile::Remove(PRBool recursive)
{
  // 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) {
    FSRef fsRef;
    rv = GetFSRefInternal(fsRef);
    if (NS_FAILED(rv))
      return rv;

    // Call MoreFilesX to do a recursive removal.
    OSStatus err = ::FSDeleteContainer(&fsRef);
    rv = MacErrorMapper(err);
  }
  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();
  }

  mCachedFSRefValid = PR_FALSE;
  return rv;
}

/* attribute unsigned long permissions; */
NS_IMETHODIMP nsLocalFile::GetPermissions(PRUint32 *aPermissions)
{
  NS_ENSURE_ARG_POINTER(aPermissions);

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

  FSCatalogInfo catalogInfo;
  OSErr err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoPermissions, &catalogInfo,
                  nsnull, nsnull, nsnull);
  if (err != noErr)
    return MacErrorMapper(err);
  FSPermissionInfo *permPtr = (FSPermissionInfo*)catalogInfo.permissions;
  *aPermissions = permPtr->mode;
  return NS_OK;
}

NS_IMETHODIMP nsLocalFile::SetPermissions(PRUint32 aPermissions)
{
  FSRef fsRef;
  nsresult rv = GetFSRefInternal(fsRef);
  if (NS_FAILED(rv))
    return rv;

  FSCatalogInfo catalogInfo;
  OSErr err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoPermissions, &catalogInfo,
                  nsnull, nsnull, nsnull);
  if (err != noErr)
    return MacErrorMapper(err);
  FSPermissionInfo *permPtr = (FSPermissionInfo*)catalogInfo.permissions;
  permPtr->mode = (UInt16)aPermissions;
  err = ::FSSetCatalogInfo(&fsRef, kFSCatInfoPermissions, &catalogInfo);
  return MacErrorMapper(err);
}

/* attribute unsigned long permissionsOfLink; */
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;
}

/* attribute PRInt64 lastModifiedTime; */
NS_IMETHODIMP nsLocalFile::GetLastModifiedTime(PRInt64 *aLastModifiedTime)
{
  // 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_IMETHODIMP nsLocalFile::SetLastModifiedTime(PRInt64 aLastModifiedTime)
{
  // 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;
}

/* attribute PRInt64 lastModifiedTimeOfLink; */
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;
}

/* attribute PRInt64 fileSize; */
NS_IMETHODIMP nsLocalFile::GetFileSize(PRInt64 *aFileSize)
{
  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_IMETHODIMP nsLocalFile::SetFileSize(PRInt64 aFileSize)
{
  // Check we are correctly initialized.
  CHECK_mBaseRef();

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

  SInt16 refNum;
  OSErr err = ::FSOpenFork(&fsRef, 0, nsnull, fsWrPerm, &refNum);
  if (err != noErr)
    return MacErrorMapper(err);
  err = ::FSSetForkSize(refNum, fsFromStart, aFileSize);
  ::FSCloseFork(refNum);

  return MacErrorMapper(err);
}

/* readonly attribute PRInt64 fileSizeOfLink; */
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);
}

/* readonly attribute AString target; */
NS_IMETHODIMP nsLocalFile::GetTarget(nsAString& aTarget)
{
    NS_ERROR("NS_ERROR_NOT_IMPLEMENTED");
    return NS_ERROR_NOT_IMPLEMENTED;
}

/* [noscript] readonly attribute ACString nativeTarget; */
NS_IMETHODIMP nsLocalFile::GetNativeTarget(nsACString& aNativeTarget)
{
    NS_ERROR("NS_ERROR_NOT_IMPLEMENTED");
    return NS_ERROR_NOT_IMPLEMENTED;
}

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

/* [noscript] readonly attribute ACString nativePath; */
NS_IMETHODIMP nsLocalFile::GetNativePath(nsACString& aNativePath)
{
  // 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;
}

/* boolean exists (); */
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, PR_TRUE))) {
    *_retval = PR_TRUE;
  }

  return NS_OK;
}

/* boolean isWritable (); */
NS_IMETHODIMP nsLocalFile::IsWritable(PRBool *_retval)
{
    // 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;
    if (::FSCheckLock(&fsRef) == noErr) {
      PRUint32 permissions;
      rv = GetPermissions(&permissions);
      if (NS_FAILED(rv))
        return rv;
      *_retval = ((permissions & S_IWUSR) != 0);
    }
    return NS_OK;
}

/* boolean isReadable (); */
NS_IMETHODIMP nsLocalFile::IsReadable(PRBool *_retval)
{
    // Check we are correctly initialized.
    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;
}

/* boolean isExecutable (); */
NS_IMETHODIMP nsLocalFile::IsExecutable(PRBool *_retval)
{
  // 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;
}

/* boolean isHidden (); */
NS_IMETHODIMP nsLocalFile::IsHidden(PRBool *_retval)
{
  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;
}

/* boolean isDirectory (); */
NS_IMETHODIMP nsLocalFile::IsDirectory(PRBool *_retval)
{
  NS_ENSURE_ARG_POINTER(_retval);
  *_retval = PR_FALSE;

  FSRef fsRef;
  nsresult rv = GetFSRefInternal(fsRef, PR_FALSE);
  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;
}

/* boolean isFile (); */
NS_IMETHODIMP nsLocalFile::IsFile(PRBool *_retval)
{
  NS_ENSURE_ARG_POINTER(_retval);
  *_retval = PR_FALSE;

  FSRef fsRef;
  nsresult rv = GetFSRefInternal(fsRef, PR_FALSE);
  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;
}

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

  NS_ENSURE_ARG(_retval);
  *_retval = PR_FALSE;

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

  FSRef fsRef;
  if (::CFURLGetFSRef(mBaseRef, &fsRef)) {
    Boolean isAlias, isFolder;
    if (::FSIsAliasFile(&fsRef, &isAlias, &isFolder) == noErr)
        *_retval = isAlias;
  }
  return NS_OK;
}

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

/* nsIFile clone (); */
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;
}

/* boolean equals (in nsIFile inFile); */
NS_IMETHODIMP nsLocalFile::Equals(nsIFile *inFile, PRBool *_retval)
{
    return EqualsInternal(inFile, PR_TRUE, _retval);
}

nsresult
nsLocalFile::EqualsInternal(nsISupports* inFile, PRBool aUpdateCache,
                            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, aUpdateCache);
  nsresult rv2 = inLF->GetFSRefInternal(inFSRef, aUpdateCache);
  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;
}

/* boolean contains (in nsIFile inFile, in boolean recur); */
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;
}

/* readonly attribute nsIFile parent; */
NS_IMETHODIMP nsLocalFile::GetParent(nsIFile * *aParent)
{
  NS_ENSURE_ARG_POINTER(aParent);
  *aParent = nsnull;

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

  nsLocalFile *newFile = nsnull;

  // 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;
    newFile = new nsLocalFile;
    if (newFile) {
      rv = newFile->InitWithCFURL(parentURLRef);
      if (NS_SUCCEEDED(rv)) {
        NS_ADDREF(*aParent = newFile);
        rv = NS_OK;
      }
    }
    }
    ::CFRelease(parentURLRef);
  }
  return rv;
}

/* readonly attribute nsISimpleEnumerator directoryEntries; */
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;
}


//*****************************************************************************
//  nsLocalFile::nsILocalFile
//*****************************************************************************
#pragma mark -
#pragma mark [nsILocalFile]

/* void initWithPath (in AString filePath); */
NS_IMETHODIMP nsLocalFile::InitWithPath(const nsAString& filePath)
{
  return InitWithNativePath(NS_ConvertUTF16toUTF8(filePath));
}

/* [noscript] void initWithNativePath (in ACString filePath); */
NS_IMETHODIMP nsLocalFile::InitWithNativePath(const nsACString& filePath)
{
  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("//", "/");

#if 1 // bird: hack to fix RegistryLocationForSpec issues with /path/to/./components
  fixedPath.ReplaceSubstring("/./", "/");
  size_t len = fixedPath.Length();
  while (len > 2)
  {
    size_t choplen = 0;
    if (!strcmp(fixedPath.get() + len - 2, "/."))
      choplen = 2;
    else if (!strcmp(fixedPath.get() + len - 1, "/"))
      choplen = 1;
    else
      break;
    fixedPath = StringHead(fixedPath, len - choplen);
  }
  // bird: another hack for the issue with VirtualBoxVM and symlinks...
  char tmpBuf[PATH_MAX];
  if (realpath(fixedPath.get(), tmpBuf))
    fixedPath = tmpBuf;
#endif

  // 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;
}

/* void initWithFile (in nsILocalFile aFile); */
NS_IMETHODIMP nsLocalFile::InitWithFile(nsILocalFile *aFile)
{
  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;
}

/* attribute PRBool followLinks; */
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;
}

/* [noscript] PRFileDescStar openNSPRFileDesc (in long flags, in long mode); */
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;
}

/* [noscript] FILE openANSIFileDesc (in string mode); */
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;
}

/* [noscript] PRLibraryStar load (); */
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;
}

/* readonly attribute PRInt64 diskSpaceAvailable; */
NS_IMETHODIMP nsLocalFile::GetDiskSpaceAvailable(PRInt64 *aDiskSpaceAvailable)
{
  // 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;
}

/* void appendRelativePath (in AString relativeFilePath); */
NS_IMETHODIMP nsLocalFile::AppendRelativePath(const nsAString& relativeFilePath)
{
  return AppendRelativeNativePath(NS_ConvertUTF16toUTF8(relativeFilePath));
}

/* [noscript] void appendRelativeNativePath (in ACString 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;
}

/* attribute ACString persistentDescriptor; */
NS_IMETHODIMP nsLocalFile::GetPersistentDescriptor(nsACString& aPersistentDescriptor)
{
  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_IMETHODIMP nsLocalFile::SetPersistentDescriptor(const nsACString& aPersistentDescriptor)
{
  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 = GetAliasSizeFromRecord(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);
}

/* void reveal (); */
NS_IMETHODIMP nsLocalFile::Reveal()
{
  FSRef             fsRefToReveal;
  AppleEvent        aeEvent = {0, nil};
  AppleEvent        aeReply = {0, nil};
  StAEDesc          aeDirDesc, listElem, myAddressDesc, fileList;
  OSErr             err;
  ProcessSerialNumber   process;

  nsresult rv = GetFSRefInternal(fsRefToReveal);
  if (NS_FAILED(rv))
    return rv;

  err = ::FindRunningAppBySignature ('MACS', process);
  if (err == noErr) {
    err = ::AECreateDesc(typeProcessSerialNumber, (Ptr)&process, sizeof(process), &myAddressDesc);
    if (err == noErr) {
      // Create the FinderEvent
      err = ::AECreateAppleEvent(kAEMiscStandards, kAEMakeObjectsVisible, &myAddressDesc,
                        kAutoGenerateReturnID, kAnyTransactionID, &aeEvent);
      if (err == noErr) {
        // Create the file list
        err = ::AECreateList(nil, 0, false, &fileList);
        if (err == noErr) {
          FSSpec fsSpecToReveal;
          err = ::FSRefMakeFSSpec(&fsRefToReveal, &fsSpecToReveal);
          if (err == noErr) {
            err = ::AEPutPtr(&fileList, 0, typeFSS, &fsSpecToReveal, sizeof(FSSpec));
            if (err == noErr) {
              err = ::AEPutParamDesc(&aeEvent, keyDirectObject, &fileList);
              if (err == noErr) {
                err = ::AESend(&aeEvent, &aeReply, kAENoReply, kAENormalPriority, kAEDefaultTimeout, nil, nil);
                if (err == noErr)
                  ::SetFrontProcess(&process);
              }
            }
          }
        }
      }
    }
  }

  return NS_OK;
}

/* void launch (); */
NS_IMETHODIMP nsLocalFile::Launch()
{
  FSRef fsRef;
  nsresult rv = GetFSRefInternal(fsRef);
  if (NS_FAILED(rv))
    return rv;

  OSErr err = ::LSOpenFSRef(&fsRef, NULL);
  return MacErrorMapper(err);
}


//*****************************************************************************
//  nsLocalFile::nsILocalFileMac
//*****************************************************************************
#pragma mark -
#pragma mark [nsILocalFileMac]

/* void initWithCFURL (in CFURLRef aCFURL); */
NS_IMETHODIMP nsLocalFile::InitWithCFURL(CFURLRef aCFURL)
{
  NS_ENSURE_ARG(aCFURL);

  SetBaseRef(aCFURL);
  return NS_OK;
}

/* void initWithFSRef ([const] in FSRefPtr aFSRef); */
NS_IMETHODIMP nsLocalFile::InitWithFSRef(const FSRef *aFSRef)
{
  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;
}

/* void initWithFSSpec ([const] in FSSpecPtr aFileSpec); */
NS_IMETHODIMP nsLocalFile::InitWithFSSpec(const FSSpec *aFileSpec)
{
  NS_ENSURE_ARG(aFileSpec);

  FSRef fsRef;
  OSErr err = ::FSpMakeFSRef(aFileSpec, &fsRef);
  if (err == noErr)
    return InitWithFSRef(&fsRef);
  else if (err == fnfErr) {
    CInfoPBRec  pBlock;
    FSSpec parentDirSpec;

    memset(&pBlock, 0, sizeof(CInfoPBRec));
    parentDirSpec.name[0] = 0;
    pBlock.dirInfo.ioVRefNum = aFileSpec->vRefNum;
    pBlock.dirInfo.ioDrDirID = aFileSpec->parID;
    pBlock.dirInfo.ioNamePtr = (StringPtr)parentDirSpec.name;
    pBlock.dirInfo.ioFDirIndex = -1;        //get info on parID
    err = ::PBGetCatInfoSync(&pBlock);
    if (err != noErr)
      return MacErrorMapper(err);

    parentDirSpec.vRefNum = aFileSpec->vRefNum;
    parentDirSpec.parID = pBlock.dirInfo.ioDrParID;
    err = ::FSpMakeFSRef(&parentDirSpec, &fsRef);
    if (err != noErr)
      return MacErrorMapper(err);
    HFSUniStr255 unicodeName;
    err = ::HFSNameGetUnicodeName(aFileSpec->name, kTextEncodingUnknown, &unicodeName);
    if (err != noErr)
      return MacErrorMapper(err);
    nsresult rv = InitWithFSRef(&fsRef);
    if (NS_FAILED(rv))
      return rv;
    return Append(nsDependentString(unicodeName.unicode, unicodeName.length));
  }
  return MacErrorMapper(err);
}

/* void initToAppWithCreatorCode (in OSType aAppCreator); */
NS_IMETHODIMP nsLocalFile::InitToAppWithCreatorCode(OSType aAppCreator)
{
  FSRef fsRef;
  OSErr err = ::LSFindApplicationForInfo(aAppCreator, nsnull, nsnull, &fsRef, nsnull);
  if (err != noErr)
    return MacErrorMapper(err);
  return InitWithFSRef(&fsRef);
}

/* CFURLRef getCFURL (); */
NS_IMETHODIMP nsLocalFile::GetCFURL(CFURLRef *_retval)
{
  NS_ENSURE_ARG_POINTER(_retval);
  CFURLRef whichURLRef = mFollowLinks ? mTargetRef : mBaseRef;
  if (whichURLRef)
    ::CFRetain(whichURLRef);
  *_retval = whichURLRef;
  return whichURLRef ? NS_OK : NS_ERROR_FAILURE;
}

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

/* FSSpec getFSSpec (); */
NS_IMETHODIMP nsLocalFile::GetFSSpec(FSSpec *_retval)
{
  NS_ENSURE_ARG_POINTER(_retval);

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

  OSErr err;
  FSRef fsRef;
  nsresult rv = GetFSRefInternal(fsRef);
  if (NS_SUCCEEDED(rv)) {
    // If the leaf node exists, things are simple.
    err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoNone,
              nsnull, nsnull, _retval, nsnull);
    return MacErrorMapper(err);
  }
  else if (rv == NS_ERROR_FILE_NOT_FOUND) {
    // If the parent of the leaf exists, make an FSSpec from that.
    CFURLRef parentURLRef = ::CFURLCreateCopyDeletingLastPathComponent(kCFAllocatorDefault, mBaseRef);
    if (!parentURLRef)
      return NS_ERROR_FAILURE;

    err = fnfErr;
    if (::CFURLGetFSRef(parentURLRef, &fsRef)) {
      FSCatalogInfo catalogInfo;
      if ((err = ::FSGetCatalogInfo(&fsRef,
                        kFSCatInfoVolume + kFSCatInfoNodeID + kFSCatInfoTextEncoding,
                        &catalogInfo, nsnull, nsnull, nsnull)) == noErr) {
        nsAutoString leafName;
        if (NS_SUCCEEDED(GetLeafName(leafName))) {
          Str31 hfsName;
          if ((err = ::UnicodeNameGetHFSName(leafName.Length(),
                          leafName.get(),
                          catalogInfo.textEncodingHint,
                          catalogInfo.nodeID == fsRtDirID,
                          hfsName)) == noErr)
            err = ::FSMakeFSSpec(catalogInfo.volume, catalogInfo.nodeID, hfsName, _retval);
        }
      }
    }
    ::CFRelease(parentURLRef);
    rv = MacErrorMapper(err);
  }
  return rv;
}

/* readonly attribute PRInt64 fileSizeWithResFork; */
NS_IMETHODIMP nsLocalFile::GetFileSizeWithResFork(PRInt64 *aFileSizeWithResFork)
{
  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;
}

/* attribute OSType fileType; */
NS_IMETHODIMP nsLocalFile::GetFileType(OSType *aFileType)
{
  NS_ENSURE_ARG_POINTER(aFileType);

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

  FinderInfo fInfo;
  OSErr err = ::FSGetFinderInfo(&fsRef, &fInfo, nsnull, nsnull);
  if (err != noErr)
    return MacErrorMapper(err);
  *aFileType = fInfo.file.fileType;
  return NS_OK;
}

NS_IMETHODIMP nsLocalFile::SetFileType(OSType aFileType)
{
  FSRef fsRef;
  nsresult rv = GetFSRefInternal(fsRef);
  if (NS_FAILED(rv))
    return rv;

  OSErr err = ::FSChangeCreatorType(&fsRef, 0, aFileType);
  return MacErrorMapper(err);
}

/* attribute OSType fileCreator; */
NS_IMETHODIMP nsLocalFile::GetFileCreator(OSType *aFileCreator)
{
  NS_ENSURE_ARG_POINTER(aFileCreator);

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

  FinderInfo fInfo;
  OSErr err = ::FSGetFinderInfo(&fsRef, &fInfo, nsnull, nsnull);
  if (err != noErr)
    return MacErrorMapper(err);
  *aFileCreator = fInfo.file.fileCreator;
  return NS_OK;
}

NS_IMETHODIMP nsLocalFile::SetFileCreator(OSType aFileCreator)
{
  FSRef fsRef;
  nsresult rv = GetFSRefInternal(fsRef);
  if (NS_FAILED(rv))
    return rv;

  OSErr err = ::FSChangeCreatorType(&fsRef, aFileCreator, 0);
  return MacErrorMapper(err);
}

/* void setFileTypeAndCreatorFromMIMEType (in string aMIMEType); */
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;
}

/* void setFileTypeAndCreatorFromExtension (in string aExtension); */
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;
}

/* void launchWithDoc (in nsILocalFile aDocToLoad, in boolean aLaunchInBackground); */
NS_IMETHODIMP nsLocalFile::LaunchWithDoc(nsILocalFile *aDocToLoad, PRBool aLaunchInBackground)
{
  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;
}

/* void openDocWithApp (in nsILocalFile aAppToOpenWith, in boolean aLaunchInBackground); */
NS_IMETHODIMP nsLocalFile::OpenDocWithApp(nsILocalFile *aAppToOpenWith, PRBool aLaunchInBackground)
{
  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;
}

/* boolean isPackage (); */
NS_IMETHODIMP nsLocalFile::IsPackage(PRBool *_retval)
{
  NS_ENSURE_ARG(_retval);
  *_retval = PR_FALSE;

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

  FSCatalogInfo catalogInfo;
  OSErr err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoNodeFlags + kFSCatInfoFinderInfo,
                                 &catalogInfo, nsnull, nsnull, nsnull);
  if (err != noErr)
    return MacErrorMapper(err);
  if ((catalogInfo.nodeFlags & kFSNodeIsDirectoryMask) != 0) {
    FileInfo *fInfoPtr = (FileInfo *)(catalogInfo.finderInfo);
    if ((fInfoPtr->finderFlags & kHasBundle) != 0) {
      *_retval = PR_TRUE;
    }
    else {
     // Folders ending with ".app" are also considered to
     // be packages, even if the top-level folder doesn't have bundle set
      nsCAutoString name;
      if (NS_SUCCEEDED(rv = GetNativeLeafName(name))) {
        const char *extPtr = strrchr(name.get(), '.');
        if (extPtr) {
          if ((nsCRT::strcasecmp(extPtr, ".app") == 0))
            *_retval = PR_TRUE;
        }
      }
    }
  }
  return NS_OK;
}

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)
{
  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;
}


//*****************************************************************************
//  nsLocalFile Methods
//*****************************************************************************
#pragma mark -
#pragma mark [Protected Methods]

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

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

  mFollowLinksDirty = PR_TRUE;
  UpdateTargetRef();
  mCachedFSRefValid = PR_FALSE;
  return NS_OK;
}

nsresult nsLocalFile::UpdateTargetRef()
{
  // 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;
}

nsresult nsLocalFile::GetFSRefInternal(FSRef& aFSRef, PRBool bForceUpdateCache)
{
  if (bForceUpdateCache || !mCachedFSRefValid) {
    mCachedFSRefValid = PR_FALSE;
    CFURLRef whichURLRef = mFollowLinks ? mTargetRef : mBaseRef;
    NS_ENSURE_TRUE(whichURLRef, NS_ERROR_NULL_POINTER);
    if (::CFURLGetFSRef(whichURLRef, &mCachedFSRef))
      mCachedFSRefValid = PR_TRUE;
  }
  if (mCachedFSRefValid) {
    aFSRef = mCachedFSRef;
    return NS_OK;
  }
  // CFURLGetFSRef only returns a Boolean for success,
  // so we have to assume what the error was. This is
  // the only probable cause.
  return NS_ERROR_FILE_NOT_FOUND;
}

nsresult nsLocalFile::GetPathInternal(nsACString& path)
{
  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;
}

nsresult nsLocalFile::CopyInternal(nsIFile* aParentDir,
                                   const nsAString& newName,
                                   PRBool followLinks)
{
  // Check we are correctly initialized.
  CHECK_mBaseRef();

  StFollowLinksState srcFollowState(*this, followLinks);

  nsresult rv;
  OSErr err;
  FSRef srcFSRef, newFSRef;

  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;

  err =
   ::FSCopyObject(&srcFSRef, &destFSRef, newName.Length(),
                  newName.Length() ? PromiseFlatString(newName).get() : NULL,
                  0, kFSCatInfoNone, false, false, NULL, NULL, &newFSRef);

  return MacErrorMapper(err);
}

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)
{
  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) {
#if 0 /* bird: too new? */
    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;
#else
    nsAutoBuffer<UInt8, FILENAME_BUFFER_SIZE> buffer;
    if (buffer.EnsureElemCapacity(usedBufLen + 1)) {
      ::CFStringGetBytes(aInStrRef, CFRangeMake(0, inStrLen),
          kCFStringEncodingUTF8, 0, false, buffer.get(), usedBufLen, &usedBufLen);
      buffer.get()[usedBufLen] = '\0';
      aOutStr.Assign(nsDependentCString((char*)buffer.get()));
      rv = NS_OK;
    }
#endif
  }
  return rv;
}

// nsIHashable

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

NS_IMETHODIMP
nsLocalFile::GetHashCode(PRUint32 *aResult)
{
    CFStringRef pathStrRef = ::CFURLCopyFileSystemPath(mBaseRef, kCFURLPOSIXPathStyle);
    nsCAutoString path;
    CFStringReftoUTF8(pathStrRef, path);
    ::CFRelease(pathStrRef);
    *aResult = HashString(path);
    return NS_OK;
}

//*****************************************************************************
//  Global Functions
//*****************************************************************************
#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_NewLocalFileWithFSSpec(const FSSpec* inSpec, PRBool followLinks, nsILocalFileMac **result)
{
    nsLocalFile* file = new nsLocalFile();
    if (file == nsnull)
        return NS_ERROR_OUT_OF_MEMORY;
    NS_ADDREF(file);

    file->SetFollowLinks(followLinks);

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

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;
}

//*****************************************************************************
//  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;
}

static OSErr FindRunningAppBySignature(OSType aAppSig, ProcessSerialNumber& outPsn)
{
  ProcessInfoRec info;
  OSErr err = noErr;

  outPsn.highLongOfPSN = 0;
  outPsn.lowLongOfPSN  = kNoProcess;

  while (PR_TRUE)
  {
    err = ::GetNextProcess(&outPsn);
    if (err == procNotFound)
      break;
    if (err != noErr)
      return err;
    info.processInfoLength = sizeof(ProcessInfoRec);
    info.processName = nil;
    info.processAppSpec = nil;
    err = ::GetProcessInformation(&outPsn, &info);
    if (err != noErr)
      return err;

    if (info.processSignature == aAppSig)
      return noErr;
  }
  return procNotFound;
}

// 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)
{
    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 {
        nsAutoBuffer<UniChar, FILENAME_BUFFER_SIZE> buffer;
        if (!buffer.EnsureElemCapacity(length))
            CopyUTF8toUTF16(aSrc, aResult);
        else {
            CFStringGetCharacters(inStr, CFRangeMake(0, length), buffer.get());
            aResult.Assign(buffer.get(), length);
        }
    }
    CFRelease(inStr);
}
