text: /*
   * Copyright 2010, The Android Open Source Project
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *  * Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *  * Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in the
   *    documentation and/or other materials provided with the distribution.
   *
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
   * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
   * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
   * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
   * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
   * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
   * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   */
  
  #include "config.h"
  #include "WebCache.h"
  
  #include "JNIUtility.h"
  #include "WebCoreJni.h"
  #include "WebRequestContext.h"
  #include "WebUrlLoaderClient.h"
  #include "net/http/http_network_session.h"
  #include <wtf/text/CString.h>
  
  using namespace WTF;
  using namespace disk_cache;
  using namespace net;
  using namespace std;
  
  namespace android {
  
  static WTF::Mutex instanceMutex;
  
  static string storageDirectory()
  {
      static const char* const kDirectory = "/webviewCacheChromium";
  
      JNIEnv* env = JSC::Bindings::getJNIEnv();
      jclass bridgeClass = env->FindClass("android/webkit/JniUtil");
      jmethodID method = env->GetStaticMethodID(bridgeClass, "getCacheDirectory", "()Ljava/lang/String;");
      string storageDirectory = jstringToStdString(env, static_cast<jstring>(env->CallStaticObjectMethod(bridgeClass, method)));
      env->DeleteLocalRef(bridgeClass);
  
      // Return empty string if storageDirectory is an empty string
      if (storageDirectory.empty())
          return storageDirectory;
  
      storageDirectory.append(kDirectory);
      return storageDirectory;
  }
  
  static scoped_refptr<WebCache>* instance(bool isPrivateBrowsing)
  {
      static scoped_refptr<WebCache> regularInstance;
      static scoped_refptr<WebCache> privateInstance;
      return isPrivateBrowsing ? &privateInstance : &regularInstance;
  }
  
  WebCache* WebCache::get(bool isPrivateBrowsing)
  {
      MutexLocker lock(instanceMutex);
      scoped_refptr<WebCache>* instancePtr = instance(isPrivateBrowsing);
      if (!instancePtr->get())
          *instancePtr = new WebCache(isPrivateBrowsing);
      return instancePtr->get();
  }
  
  void WebCache::cleanup(bool isPrivateBrowsing)
  {
      MutexLocker lock(instanceMutex);
      scoped_refptr<WebCache>* instancePtr = instance(isPrivateBrowsing);
      *instancePtr = 0;
  }
  
  WebCache::WebCache(bool isPrivateBrowsing)
      : m_doomAllEntriesCallback(this, &WebCache::doomAllEntries)
      , m_onClearDoneCallback(this, &WebCache::onClearDone)
      , m_isClearInProgress(false)
      , m_openEntryCallback(this, &WebCache::openEntry)
      , m_onGetEntryDoneCallback(this, &WebCache::onGetEntryDone)
      , m_isGetEntryInProgress(false)
      , m_cacheBackend(0)
  {
      base::Thread* ioThread = WebUrlLoaderClient::ioThread();
      scoped_refptr<base::MessageLoopProxy> cacheMessageLoopProxy = ioThread->message_loop_proxy();
  
      static const int kMaximumCacheSizeBytes = 20 * 1024 * 1024;
      m_hostResolver = net::CreateSystemHostResolver(net::HostResolver::kDefaultParallelism, 0, 0);
  
      m_proxyConfigService = new ProxyConfigServiceAndroid();
      net::HttpCache::BackendFactory* backendFactory;
      if (isPrivateBrowsing)
          backendFactory = net::HttpCache::DefaultBackend::InMemory(kMaximumCacheSizeBytes / 2);
      else {
          string storage(storageDirectory());
          if (storage.empty()) // Can't get a storage directory from the OS
              backendFactory = net::HttpCache::DefaultBackend::InMemory(kMaximumCacheSizeBytes / 2);
          else {
              FilePath directoryPath(storage.c_str());
              backendFactory = new net::HttpCache::DefaultBackend(net::DISK_CACHE, directoryPath, kMaximumCacheSizeBytes, cacheMessageLoopProxy);
          }
      }
  
      m_cache = new net::HttpCache(m_hostResolver.get(),
                                   new CertVerifier(),
                                   0, // dnsrr_resolver
                                   0, // dns_cert_checker
                                   net::ProxyService::CreateWithoutProxyResolver(m_proxyConfigService, 0 /* net_log */),
                                   net::SSLConfigService::CreateSystemSSLConfigService(),
                                   net::HttpAuthHandlerFactory::CreateDefault(m_hostResolver.get()),
                                   0, // network_delegate
                                   0, // net_log
                                   backendFactory);
  }
  
  void WebCache::clear()
  {
      base::Thread* thread = WebUrlLoaderClient::ioThread();
      if (thread)
          thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this, &WebCache::clearImpl));
  }
  
  void WebCache::certTrustChanged()
  {
      base::Thread* thread = WebUrlLoaderClient::ioThread();
      if (thread)
          thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this, &WebCache::certTrustChangedImpl));
  }
  
  void WebCache::certTrustChangedImpl()
  {
      net::HttpNetworkSession* session = m_cache->GetSession();
      if (session)
          session->cert_verifier()->ClearCache();
      m_cache->CloseAllConnections();
  }
  
  void WebCache::closeIdleConnections()
  {
      base::Thread* thread = WebUrlLoaderClient::ioThread();
      if (thread)
          thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this, &WebCache::closeIdleImpl));
  }
  
  void WebCache::closeIdleImpl()
  {
      m_cache->CloseIdleConnections();
  }
  
  void WebCache::clearImpl()
  {
      if (m_isClearInProgress)
          return;
      m_isClearInProgress = true;
  
      if (!m_cacheBackend) {
          int code = m_cache->GetBackend(&m_cacheBackend, &m_doomAllEntriesCallback);
          // Code ERR_IO_PENDING indicates that the operation is still in progress and
          // the supplied callback will be invoked when it completes.
          if (code == ERR_IO_PENDING)
              return;
          else if (code != OK) {
              onClearDone(0 /*unused*/);
              return;
          }
      }
      doomAllEntries(0 /*unused*/);
  }
  
  void WebCache::doomAllEntries(int)
  {
      if (!m_cacheBackend) {
          onClearDone(0 /*unused*/);
          return;
      }
  
      // Code ERR_IO_PENDING indicates that the operation is still in progress and
      // the supplied callback will be invoked when it completes.
      if (m_cacheBackend->DoomAllEntries(&m_onClearDoneCallback) == ERR_IO_PENDING)
          return;
      onClearDone(0 /*unused*/);
  }
  
  void WebCache::onClearDone(int)
  {
      m_isClearInProgress = false;
  }
  
  scoped_refptr<CacheResult> WebCache::getCacheResult(String url)
  {
      // This is called on the UI thread.
      MutexLocker lock(m_getEntryMutex);
      if (m_isGetEntryInProgress)
          return 0; // TODO: OK? Or can we queue 'em up?
  
      // The Chromium methods are asynchronous, but we need this method to be
      // synchronous. Do the work on the Chromium thread but block this thread
      // here waiting for the work to complete.
      base::Thread* thread = WebUrlLoaderClient::ioThread();
      if (!thread)
          return 0;
  
      m_entry = 0;
      m_isGetEntryInProgress = true;
      m_entryUrl = url.threadsafeCopy();
      thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this, &WebCache::getEntryImpl));
  
      while (m_isGetEntryInProgress)
          m_getEntryCondition.wait(m_getEntryMutex);
  
      if (!m_entry)
          return 0;
  
      return new CacheResult(m_entry, url);
  }
  
  void WebCache::getEntryImpl()
  {
      if (!m_cacheBackend) {
          int code = m_cache->GetBackend(&m_cacheBackend, &m_openEntryCallback);
          if (code == ERR_IO_PENDING)
              return;
          else if (code != OK) {
              onGetEntryDone(0 /*unused*/);
              return;
          }
      }
      openEntry(0 /*unused*/);
  }
  
  void WebCache::openEntry(int)
  {
      if (!m_cacheBackend) {
          onGetEntryDone(0 /*unused*/);
          return;
      }
  
      if (m_cacheBackend->OpenEntry(string(m_entryUrl.utf8().data()), &m_entry, &m_onGetEntryDoneCallback) == ERR_IO_PENDING)
          return;
      onGetEntryDone(0 /*unused*/);
  }
  
  void WebCache::onGetEntryDone(int)
  {
      // Unblock the UI thread in getEntry();
      MutexLocker lock(m_getEntryMutex);
      m_isGetEntryInProgress = false;
      m_getEntryCondition.signal();
  }
  
  } // namespace android
  