/* 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 "base/task.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"
#include "AutoMounter.h"
#include "VolumeManager.h"

#undef VOLUME_MANAGER_LOG_TAG
#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";
    case nsIVolume::STATE_CHECKMNT:   return "Check-Mounted";
    case nsIVolume::STATE_MOUNT_FAIL: return "Mount-Fail";
  }
  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_ISUPPORTS(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()),
    mIsFake(!aVolume->CanBeShared()),
    mIsMediaPresent(aVolume->MediaPresent()),
    mIsSharing(aVolume->IsSharing()),
    mIsFormatting(aVolume->IsFormatting()),
    mIsUnmounting(aVolume->IsUnmounting()),
    mIsRemovable(aVolume->IsRemovable()),
    mIsHotSwappable(aVolume->IsHotSwappable())
{
}

nsVolume::nsVolume(const nsVolume* aVolume)
  : mName(aVolume->mName),
    mMountPoint(aVolume->mMountPoint),
    mState(aVolume->mState),
    mMountGeneration(aVolume->mMountGeneration),
    mMountLocked(aVolume->mMountLocked),
    mIsFake(aVolume->mIsFake),
    mIsMediaPresent(aVolume->mIsMediaPresent),
    mIsSharing(aVolume->mIsSharing),
    mIsFormatting(aVolume->mIsFormatting),
    mIsUnmounting(aVolume->mIsUnmounting),
    mIsRemovable(aVolume->mIsRemovable),
    mIsHotSwappable(aVolume->mIsHotSwappable)
{
}

void nsVolume::Dump(const char* aLabel) const
{
  LOG("%s: Volume: %s is %s and %s @ %s gen %d locked %d",
      aLabel,
      NameStr().get(),
      StateStr(),
      IsMediaPresent() ? "inserted" : "missing",
      MountPointStr().get(),
      MountGeneration(),
      (int)IsMountLocked());
  LOG("%s:   IsSharing %s IsFormating %s IsUnmounting %s",
      aLabel,
      (IsSharing() ? "y" : "n"),
      (IsFormatting() ? "y" : "n"),
      (IsUnmounting() ? "y" : "n"));
}

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

  bool isFake;
  aVolume->GetIsFake(&isFake);
  if (mIsFake != isFake) {
    return false;
  }

  bool isSharing;
  aVolume->GetIsSharing(&isSharing);
  if (mIsSharing != isSharing) {
    return false;
  }

  bool isFormatting;
  aVolume->GetIsFormatting(&isFormatting);
  if (mIsFormatting != isFormatting) {
    return false;
  }

  bool isUnmounting;
  aVolume->GetIsUnmounting(&isUnmounting);
  if (mIsUnmounting != isUnmounting) {
    return false;
  }

  bool isRemovable;
  aVolume->GetIsRemovable(&isRemovable);
  if (mIsRemovable != isRemovable) {
    return false;
  }

  bool isHotSwappable;
  aVolume->GetIsHotSwappable(&isHotSwappable);
  if (mIsHotSwappable != isHotSwappable) {
    return false;
  }

  return true;
}

NS_IMETHODIMP nsVolume::GetIsMediaPresent(bool* aIsMediaPresent)
{
  *aIsMediaPresent = mIsMediaPresent;
  return NS_OK;
}

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

NS_IMETHODIMP nsVolume::GetIsSharing(bool* aIsSharing)
{
  *aIsSharing = mIsSharing;
  return NS_OK;
}

NS_IMETHODIMP nsVolume::GetIsFormatting(bool* aIsFormatting)
{
  *aIsFormatting = mIsFormatting;
  return NS_OK;
}

NS_IMETHODIMP nsVolume::GetIsUnmounting(bool* aIsUnmounting)
{
  *aIsUnmounting = mIsUnmounting;
  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;
}

NS_IMETHODIMP nsVolume::GetIsFake(bool *aIsFake)
{
  *aIsFake = mIsFake;
  return NS_OK;
}

NS_IMETHODIMP nsVolume::GetIsRemovable(bool *aIsRemovable)
{
  *aIsRemovable = mIsRemovable;
  return NS_OK;
}

NS_IMETHODIMP nsVolume::GetIsHotSwappable(bool *aIsHotSwappable)
{
  *aIsHotSwappable = mIsHotSwappable;
  return NS_OK;
}

NS_IMETHODIMP nsVolume::Format()
{
  MOZ_ASSERT(XRE_IsParentProcess());

  XRE_GetIOMessageLoop()->PostTask(
      NewRunnableFunction(FormatVolumeIOThread, NameStr()));

  return NS_OK;
}

/* static */
void nsVolume::FormatVolumeIOThread(const nsCString& aVolume)
{
  MOZ_ASSERT(XRE_IsParentProcess());

  MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
  if (VolumeManager::State() != VolumeManager::VOLUMES_READY) {
    return;
  }

  AutoMounterFormatVolume(aVolume);
}

NS_IMETHODIMP nsVolume::Mount()
{
  MOZ_ASSERT(XRE_IsParentProcess());

  XRE_GetIOMessageLoop()->PostTask(
      NewRunnableFunction(MountVolumeIOThread, NameStr()));

  return NS_OK;
}

/* static */
void nsVolume::MountVolumeIOThread(const nsCString& aVolume)
{
  MOZ_ASSERT(XRE_IsParentProcess());

  MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
  if (VolumeManager::State() != VolumeManager::VOLUMES_READY) {
    return;
  }

  AutoMounterMountVolume(aVolume);
}

NS_IMETHODIMP nsVolume::Unmount()
{
  MOZ_ASSERT(XRE_IsParentProcess());

  XRE_GetIOMessageLoop()->PostTask(
      NewRunnableFunction(UnmountVolumeIOThread, NameStr()));

  return NS_OK;
}

/* static */
void nsVolume::UnmountVolumeIOThread(const nsCString& aVolume)
{
  MOZ_ASSERT(XRE_IsParentProcess());

  MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
  if (VolumeManager::State() != VolumeManager::VOLUMES_READY) {
    return;
  }

  AutoMounterUnmountVolume(aVolume);
}

void
nsVolume::LogState() const
{
  if (mState == nsIVolume::STATE_MOUNTED) {
    LOG("nsVolume: %s state %s @ '%s' gen %d locked %d fake %d "
        "media %d sharing %d formatting %d unmounting %d removable %d hotswappable %d",
        NameStr().get(), StateStr(), MountPointStr().get(),
        MountGeneration(), (int)IsMountLocked(), (int)IsFake(),
        (int)IsMediaPresent(), (int)IsSharing(),
        (int)IsFormatting(), (int)IsUnmounting(),
        (int)IsRemovable(), (int)IsHotSwappable());
    return;
  }

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

void nsVolume::UpdateMountLock(nsVolume* aOldVolume)
{
  MOZ_ASSERT(NS_IsMainThread());

  bool oldMountLocked = aOldVolume ? aOldVolume->mMountLocked : false;
  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;
    mMountLocked = oldMountLocked;
    return;
  }

  int32_t oldMountGeneration = aOldVolume ? aOldVolume->mMountGeneration : -1;
  if (mMountGeneration == oldMountGeneration) {
    // No change in mount generation, nothing else to do
    mMountLocked = oldMountLocked;
    return;
  }

  if (!XRE_IsParentProcess()) {
    // Child processes just track the state, not maintain it.
    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_IsParentProcess());
  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_IsParentProcess());
  MOZ_ASSERT(NS_IsMainThread());

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

void
nsVolume::SetIsFake(bool aIsFake)
{
  mIsFake = aIsFake;
  if (mIsFake) {
    // The media is always present for fake volumes.
    mIsMediaPresent = true;
    MOZ_ASSERT(!mIsSharing);
  }
}

void
nsVolume::SetIsRemovable(bool aIsRemovable)
{
  mIsRemovable = aIsRemovable;
  if (!mIsRemovable) {
    mIsHotSwappable = false;
  }
}

void
nsVolume::SetIsHotSwappable(bool aIsHotSwappable)
{
  mIsHotSwappable = aIsHotSwappable;
  if (mIsHotSwappable) {
    mIsRemovable = true;
  }
}

void
nsVolume::SetState(int32_t aState)
{
  static int32_t sMountGeneration = 0;

  MOZ_ASSERT(XRE_IsParentProcess());
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(IsFake());

  if (aState == mState) {
    return;
  }

  if (aState == nsIVolume::STATE_MOUNTED) {
    mMountGeneration = ++sMountGeneration;
  }

  mState = aState;
}

} // system
} // mozilla
