/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */

#ifndef mozilla_dom_cache_Context_h
#define mozilla_dom_cache_Context_h

#include "mozilla/dom/cache/Types.h"
#include "nsCOMPtr.h"
#include "nsISupportsImpl.h"
#include "nsProxyRelease.h"
#include "nsString.h"
#include "nsTArray.h"
#include "nsTObserverArray.h"

class nsIEventTarget;
class nsIThread;

namespace mozilla {
namespace dom {

namespace quota {

class DirectoryLock;

} // namespace quota

namespace cache {

class Action;
class Manager;

// The Context class is RAII-style class for managing IO operations within the
// Cache.
//
// When a Context is created it performs the complicated steps necessary to
// initialize the QuotaManager.  Action objects dispatched on the Context are
// delayed until this initialization is complete.  They are then allow to
// execute on any specified thread.  Once all references to the Context are
// gone, then the steps necessary to release the QuotaManager are performed.
// After initialization the Context holds a self reference, so it will stay
// alive until one of three conditions occur:
//
//  1) The Manager will call Context::AllowToClose() when all of the actors
//     have removed themselves as listener.  This means an idle context with
//     no active DOM objects will close gracefully.
//  2) The QuotaManager aborts all operations so it can delete the files.
//     In this case the QuotaManager calls Client::AbortOperations() which
//     in turn cancels all existing Action objects and then marks the Manager
//     as invalid.
//  3) Browser shutdown occurs and the Manager calls Context::CancelAll().
//
// In either case, though, the Action objects must be destroyed first to
// allow the Context to be destroyed.
//
// While the Context performs operations asynchronously on threads, all of
// methods in its public interface must be called on the same thread
// originally used to create the Context.
//
// As an invariant, all Context objects must be destroyed before permitting
// the "profile-before-change" shutdown event to complete.  This is ensured
// via the code in ShutdownObserver.cpp.
class Context final
{
  typedef mozilla::dom::quota::DirectoryLock DirectoryLock;

public:
  // Define a class allowing other threads to hold the Context alive.  This also
  // allows these other threads to safely close or cancel the Context.
  class ThreadsafeHandle final
  {
    friend class Context;
  public:
    void AllowToClose();
    void InvalidateAndAllowToClose();
  private:
    explicit ThreadsafeHandle(Context* aContext);
    ~ThreadsafeHandle();

    // disallow copying
    ThreadsafeHandle(const ThreadsafeHandle&) = delete;
    ThreadsafeHandle& operator=(const ThreadsafeHandle&) = delete;

    void AllowToCloseOnOwningThread();
    void InvalidateAndAllowToCloseOnOwningThread();

    void ContextDestroyed(Context* aContext);

    // Cleared to allow the Context to close.  Only safe to access on
    // owning thread.
    RefPtr<Context> mStrongRef;

    // Used to support cancelation even while the Context is already allowed
    // to close.  Cleared by ~Context() calling ContextDestroyed().  Only
    // safe to access on owning thread.
    Context* mWeakRef;

    nsCOMPtr<nsIThread> mOwningThread;

    NS_INLINE_DECL_THREADSAFE_REFCOUNTING(cache::Context::ThreadsafeHandle)
  };

  // Different objects hold references to the Context while some work is being
  // performed asynchronously.  These objects must implement the Activity
  // interface and register themselves with the AddActivity().  When they are
  // destroyed they must call RemoveActivity().  This allows the Context to
  // cancel any outstanding Activity work when the Context is cancelled.
  class Activity
  {
  public:
    virtual void Cancel() = 0;
    virtual bool MatchesCacheId(CacheId aCacheId) const = 0;
  };

  // Create a Context attached to the given Manager.  The given Action
  // will run on the QuotaManager IO thread.  Note, this Action must
  // be execute synchronously.
  static already_AddRefed<Context>
  Create(Manager* aManager, nsIThread* aTarget,
         Action* aInitAction, Context* aOldContext);

  // Execute given action on the target once the quota manager has been
  // initialized.
  //
  // Only callable from the thread that created the Context.
  void Dispatch(Action* aAction);

  // Cancel any Actions running or waiting to run.  This should allow the
  // Context to be released and Listener::RemoveContext() will be called
  // when complete.
  //
  // Only callable from the thread that created the Context.
  void CancelAll();

  // True if CancelAll() has been called.
  bool IsCanceled() const;

  // Like CancelAll(), but also marks the Manager as "invalid".
  void Invalidate();

  // Remove any self references and allow the Context to be released when
  // there are no more Actions to process.
  void AllowToClose();

  // Cancel any Actions running or waiting to run that operate on the given
  // cache ID.
  //
  // Only callable from the thread that created the Context.
  void CancelForCacheId(CacheId aCacheId);

  void AddActivity(Activity* aActivity);
  void RemoveActivity(Activity* aActivity);

  const QuotaInfo&
  GetQuotaInfo() const
  {
    return mQuotaInfo;
  }

  // Tell the Context that some state information has been orphaned in the
  // data store and won't be cleaned up.  The Context will leave the marker
  // in place to trigger cleanup the next times its opened.
  void NoteOrphanedData();

private:
  class Data;
  class QuotaInitRunnable;
  class ActionRunnable;

  enum State
  {
    STATE_CONTEXT_PREINIT,
    STATE_CONTEXT_INIT,
    STATE_CONTEXT_READY,
    STATE_CONTEXT_CANCELED
  };

  struct PendingAction
  {
    nsCOMPtr<nsIEventTarget> mTarget;
    RefPtr<Action> mAction;
  };

  Context(Manager* aManager, nsIThread* aTarget, Action* aInitAction);
  ~Context();
  void Init(Context* aOldContext);
  void Start();
  void DispatchAction(Action* aAction, bool aDoomData = false);
  void OnQuotaInit(nsresult aRv, const QuotaInfo& aQuotaInfo,
                   already_AddRefed<DirectoryLock> aDirectoryLock);


  already_AddRefed<ThreadsafeHandle>
  CreateThreadsafeHandle();

  void
  SetNextContext(Context* aNextContext);

  void
  DoomTargetData();

  RefPtr<Manager> mManager;
  nsCOMPtr<nsIThread> mTarget;
  RefPtr<Data> mData;
  State mState;
  bool mOrphanedData;
  QuotaInfo mQuotaInfo;
  RefPtr<QuotaInitRunnable> mInitRunnable;
  RefPtr<Action> mInitAction;
  nsTArray<PendingAction> mPendingActions;

  // Weak refs since activites must remove themselves from this list before
  // being destroyed by calling RemoveActivity().
  typedef nsTObserverArray<Activity*> ActivityList;
  ActivityList mActivityList;

  // The ThreadsafeHandle may have a strong ref back to us.  This creates
  // a ref-cycle that keeps the Context alive.  The ref-cycle is broken
  // when ThreadsafeHandle::AllowToClose() is called.
  RefPtr<ThreadsafeHandle> mThreadsafeHandle;

  RefPtr<DirectoryLock> mDirectoryLock;
  RefPtr<Context> mNextContext;

public:
  NS_INLINE_DECL_REFCOUNTING(cache::Context)
};

} // namespace cache
} // namespace dom
} // namespace mozilla

#endif // mozilla_dom_cache_Context_h