/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "nsVolume.h"

#include "base/message_loop.h"
#include "nsIPowerManagerService.h"
#include "nsISupportsUtils.h"
#include "nsIVolume.h"
#include "nsServiceManagerUtils.h"
#include "nsThreadUtils.h"
#include "nsVolumeStat.h"
#include "nsXULAppAPI.h"
#include "Volume.h"

#define VOLUME_MANAGER_LOG_TAG  "nsVolume"
#include "VolumeManagerLog.h"

namespace mozilla {
namespace system {

const char *
NS_VolumeStateStr(int32_t aState)
{
  switch (aState) {
    case nsIVolume::STATE_INIT:       return "Init";
    case nsIVolume::STATE_NOMEDIA:    return "NoMedia";
    case nsIVolume::STATE_IDLE:       return "Idle";
    case nsIVolume::STATE_PENDING:    return "Pending";
    case nsIVolume::STATE_CHECKING:   return "Checking";
    case nsIVolume::STATE_MOUNTED:    return "Mounted";
    case nsIVolume::STATE_UNMOUNTING: return "Unmounting";
    case nsIVolume::STATE_FORMATTING: return "Formatting";
    case nsIVolume::STATE_SHARED:     return "Shared";
    case nsIVolume::STATE_SHAREDMNT:  return "Shared-Mounted";
  }
  return "???";
}

// While nsVolumes can only be used on the main thread, in the
// UpdateVolumeRunnable constructor (which is called from IOThread) we
// allocate an nsVolume which is then passed to MainThread. Since we
// have a situation where we allocate on one thread and free on another
// we use a thread safe AddRef implementation.
NS_IMPL_THREADSAFE_ISUPPORTS1(nsVolume, nsIVolume)

nsVolume::nsVolume(const Volume* aVolume)
  : mName(NS_ConvertUTF8toUTF16(aVolume->Name())),
    mMountPoint(NS_ConvertUTF8toUTF16(aVolume->MountPoint())),
    mState(aVolume->State()),
    mMountGeneration(aVolume->MountGeneration()),
    mMountLocked(aVolume->IsMountLocked())
{
}

bool nsVolume::Equals(nsIVolume* aVolume)
{
  nsString volName;
  aVolume->GetName(volName);
  if (!mName.Equals(volName)) {
    return false;
  }

  nsString volMountPoint;
  aVolume->GetMountPoint(volMountPoint);
  if (!mMountPoint.Equals(volMountPoint)) {
    return false;
  }

  int32_t volState;
  aVolume->GetState(&volState);
  if (mState != volState){
    return false;
  }

  int32_t volMountGeneration;
  aVolume->GetMountGeneration(&volMountGeneration);
  if (mMountGeneration != volMountGeneration) {
    return false;
  }

  bool volIsMountLocked;
  aVolume->GetIsMountLocked(&volIsMountLocked);
  if (mMountLocked != volIsMountLocked) {
    return false;
  }
  return true;
}

NS_IMETHODIMP nsVolume::GetIsMountLocked(bool *aIsMountLocked)
{
  *aIsMountLocked = mMountLocked;
  return NS_OK;
}

NS_IMETHODIMP nsVolume::GetName(nsAString& aName)
{
  aName = mName;
  return NS_OK;
}

NS_IMETHODIMP nsVolume::GetMountGeneration(int32_t* aMountGeneration)
{
  *aMountGeneration = mMountGeneration;
  return NS_OK;
}

NS_IMETHODIMP nsVolume::GetMountLockName(nsAString& aMountLockName)
{
  aMountLockName = NS_LITERAL_STRING("volume-") + Name();
  aMountLockName.AppendPrintf("-%d", mMountGeneration);

  return NS_OK;
}

NS_IMETHODIMP nsVolume::GetMountPoint(nsAString& aMountPoint)
{
  aMountPoint = mMountPoint;
  return NS_OK;
}

NS_IMETHODIMP nsVolume::GetState(int32_t* aState)
{
  *aState = mState;
  return NS_OK;
}

NS_IMETHODIMP nsVolume::GetStats(nsIVolumeStat **aResult)
{
  if (mState != STATE_MOUNTED) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  NS_IF_ADDREF(*aResult = new nsVolumeStat(mMountPoint));
  return NS_OK;
}

void
nsVolume::LogState() const
{
  if (mState == nsIVolume::STATE_MOUNTED) {
    LOG("nsVolume: %s state %s @ '%s' gen %d locked %d",
        NameStr().get(), StateStr(), MountPointStr().get(),
        MountGeneration(), (int)IsMountLocked());
    return;
  }

  LOG("nsVolume: %s state %s", NameStr().get(), StateStr());
}

void nsVolume::Set(nsIVolume* aVolume)
{
  MOZ_ASSERT(NS_IsMainThread());

  aVolume->GetName(mName);
  aVolume->GetMountPoint(mMountPoint);
  aVolume->GetState(&mState);

  int32_t volMountGeneration;
  aVolume->GetMountGeneration(&volMountGeneration);

  if (mState != nsIVolume::STATE_MOUNTED) {
    // Since we're not in the mounted state, we need to
    // forgot whatever mount generation we may have had.
    mMountGeneration = -1;
    return;
  }
  if (mMountGeneration == volMountGeneration) {
    // No change in mount generation, nothing else to do
    return;
  }

  mMountGeneration = volMountGeneration;

  if (XRE_GetProcessType() != GoannaProcessType_Default) {
    // Child processes just track the state, not maintain it.
    aVolume->GetIsMountLocked(&mMountLocked);
    return;
  }

  // Notify the Volume on IOThread whether the volume is locked or not.
  nsCOMPtr<nsIPowerManagerService> pmService =
    do_GetService(POWERMANAGERSERVICE_CONTRACTID);
  if (!pmService) {
    return;
  }
  nsString mountLockName;
  GetMountLockName(mountLockName);
  nsString mountLockState;
  pmService->GetWakeLockState(mountLockName, mountLockState);
  UpdateMountLock(mountLockState);
}

void
nsVolume::UpdateMountLock(const nsAString& aMountLockState)
{
  MOZ_ASSERT(XRE_GetProcessType() == GoannaProcessType_Default);
  MOZ_ASSERT(NS_IsMainThread());

  // There are 3 states, unlocked, locked-background, and locked-foreground
  // I figured it was easier to use negtive logic and compare for unlocked.
  UpdateMountLock(!aMountLockState.EqualsLiteral("unlocked"));
}

void
nsVolume::UpdateMountLock(bool aMountLocked)
{
  MOZ_ASSERT(XRE_GetProcessType() == GoannaProcessType_Default);
  MOZ_ASSERT(NS_IsMainThread());

  if (aMountLocked == mMountLocked) {
    return;
  }
  // The locked/unlocked state changed. Tell IOThread about it.
  mMountLocked = aMountLocked;
  LogState();
  XRE_GetIOMessageLoop()->PostTask(
     FROM_HERE,
     NewRunnableFunction(Volume::UpdateMountLock,
                         NS_LossyConvertUTF16toASCII(Name()),
                         MountGeneration(), aMountLocked));
}

} // system
} // mozilla
