/* 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 "nsVolumeMountLock.h"

#include "mozilla/dom/ContentChild.h"
#include "mozilla/Services.h"

#include "nsIObserverService.h"
#include "nsIPowerManagerService.h"
#include "nsIVolume.h"
#include "nsIVolumeService.h"
#include "nsString.h"
#include "nsXULAppAPI.h"

#undef VOLUME_MANAGER_LOG_TAG
#define VOLUME_MANAGER_LOG_TAG  "nsVolumeMountLock"
#include "VolumeManagerLog.h"
#include "nsServiceManagerUtils.h"
#include "mozilla/dom/power/PowerManagerService.h"

using namespace mozilla::dom;
using namespace mozilla::services;

namespace mozilla {
namespace system {

NS_IMPL_ISUPPORTS(nsVolumeMountLock, nsIVolumeMountLock,
                  nsIObserver, nsISupportsWeakReference)

// static
already_AddRefed<nsVolumeMountLock>
nsVolumeMountLock::Create(const nsAString& aVolumeName)
{
  DBG("nsVolumeMountLock::Create called");

  RefPtr<nsVolumeMountLock> mountLock = new nsVolumeMountLock(aVolumeName);
  nsresult rv = mountLock->Init();
  NS_ENSURE_SUCCESS(rv, nullptr);

  return mountLock.forget();
}

nsVolumeMountLock::nsVolumeMountLock(const nsAString& aVolumeName)
  : mVolumeName(aVolumeName),
    mVolumeGeneration(-1),
    mUnlocked(false)
{
}

//virtual
nsVolumeMountLock::~nsVolumeMountLock()
{
  Unlock();
}

nsresult nsVolumeMountLock::Init()
{
  LOG("nsVolumeMountLock created for '%s'",
      NS_LossyConvertUTF16toASCII(mVolumeName).get());

  // Add ourselves as an Observer. It's important that we use a weak
  // reference here. If we used a strong reference, then that reference
  // would prevent this object from being destructed.
  nsCOMPtr<nsIObserverService> obs = GetObserverService();
  obs->AddObserver(this, NS_VOLUME_STATE_CHANGED, true /*weak*/);

  // Get the initial mountGeneration and grab a lock.
  nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
  NS_ENSURE_TRUE(vs, NS_ERROR_FAILURE);

  nsCOMPtr<nsIVolume> vol;
  nsresult rv = vs->GetVolumeByName(mVolumeName, getter_AddRefs(vol));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }
  rv = vol->GetMountGeneration(&mVolumeGeneration);
  NS_ENSURE_SUCCESS(rv, rv);

  return Lock(vol);
}

NS_IMETHODIMP nsVolumeMountLock::Unlock()
{
  LOG("nsVolumeMountLock released for '%s'",
      NS_LossyConvertUTF16toASCII(mVolumeName).get());

  mUnlocked = true;
  mWakeLock = nullptr;

  // While we don't really need to remove weak observers, we do so anyways
  // since it will reduce the number of times Observe gets called.
  nsCOMPtr<nsIObserverService> obs = GetObserverService();
  obs->RemoveObserver(this, NS_VOLUME_STATE_CHANGED);
  return NS_OK;
}

NS_IMETHODIMP nsVolumeMountLock::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData)
{
  if (strcmp(aTopic, NS_VOLUME_STATE_CHANGED) != 0) {
    return NS_OK;
  }
  if (mUnlocked) {
    // We're not locked anymore, so we don't need to look at the notifications.
    return NS_OK;
  }

  nsCOMPtr<nsIVolume> vol = do_QueryInterface(aSubject);
  if (!vol) {
    return NS_OK;
  }
  nsString volName;
  vol->GetName(volName);
  if (!volName.Equals(mVolumeName)) {
    return NS_OK;
  }
  int32_t state;
  nsresult rv = vol->GetState(&state);
  NS_ENSURE_SUCCESS(rv, rv);

  if (state != nsIVolume::STATE_MOUNTED) {
    mWakeLock = nullptr;
    mVolumeGeneration = -1;
    return NS_OK;
  }

  int32_t   mountGeneration;
  rv = vol->GetMountGeneration(&mountGeneration);
  NS_ENSURE_SUCCESS(rv, rv);

  DBG("nsVolumeMountLock::Observe mountGeneration = %d mVolumeGeneration = %d",
       mountGeneration, mVolumeGeneration);

  if (mVolumeGeneration == mountGeneration) {
    return NS_OK;
  }

  // The generation changed, which means that any wakelock we may have
  // been holding is now invalid. Grab a new wakelock for the new generation
  // number.

  mWakeLock = nullptr;
  mVolumeGeneration = mountGeneration;

  return Lock(vol);
}

nsresult
nsVolumeMountLock::Lock(nsIVolume* aVolume)
{
  RefPtr<power::PowerManagerService> pmService =
    power::PowerManagerService::GetInstance();
  NS_ENSURE_TRUE(pmService, NS_ERROR_FAILURE);

  nsString mountLockName;
  aVolume->GetMountLockName(mountLockName);

  ErrorResult err;
  mWakeLock = pmService->NewWakeLock(mountLockName, nullptr, err);
  if (err.Failed()) {
    return err.StealNSResult();
  }

  LOG("nsVolumeMountLock acquired for '%s' gen %d",
      NS_LossyConvertUTF16toASCII(mVolumeName).get(), mVolumeGeneration);
  return NS_OK;
}

} // namespace system
} // namespace mozilla