Almost there...



git-svn-id: svn://chrome-svn/chromeos/trunk@24 06c00378-0e64-4dae-be16-12b19f9950a1
diff --git a/libcurl_http_fetcher.cc b/libcurl_http_fetcher.cc
new file mode 100644
index 0000000..ab48694
--- /dev/null
+++ b/libcurl_http_fetcher.cc
@@ -0,0 +1,226 @@
+// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "glog/logging.h"
+#include "update_engine/libcurl_http_fetcher.h"
+
+// This is a concrete implementation of HttpFetcher that uses libcurl to do the
+// http work.
+
+namespace chromeos_update_engine {
+
+LibcurlHttpFetcher::~LibcurlHttpFetcher() {
+  CleanUp();
+}
+
+// Begins the transfer, which must not have already been started.
+void LibcurlHttpFetcher::BeginTransfer(const std::string& url) {
+  CHECK(!transfer_in_progress_);
+  url_ = url;
+  curl_multi_handle_ = curl_multi_init();
+  CHECK(curl_multi_handle_);
+
+  curl_handle_ = curl_easy_init();
+  CHECK(curl_handle_);
+
+  if (post_data_set_) {
+    CHECK_EQ(CURLE_OK, curl_easy_setopt(curl_handle_, CURLOPT_POST, 1));
+    CHECK_EQ(CURLE_OK, curl_easy_setopt(curl_handle_, CURLOPT_POSTFIELDS,
+                                        &post_data_[0]));
+    CHECK_EQ(CURLE_OK, curl_easy_setopt(curl_handle_, CURLOPT_POSTFIELDSIZE,
+                                        post_data_.size()));
+  }
+
+  CHECK_EQ(CURLE_OK, curl_easy_setopt(curl_handle_, CURLOPT_WRITEDATA, this));
+  CHECK_EQ(CURLE_OK, curl_easy_setopt(curl_handle_, CURLOPT_WRITEFUNCTION,
+                                      StaticLibcurlWrite));
+  CHECK_EQ(CURLE_OK, curl_easy_setopt(curl_handle_, CURLOPT_URL, url_.c_str()));
+  CHECK_EQ(CURLM_OK, curl_multi_add_handle(curl_multi_handle_, curl_handle_));
+  transfer_in_progress_ = true;
+  CurlPerformOnce();
+}
+
+void LibcurlHttpFetcher::TerminateTransfer() {
+  CleanUp();
+}
+
+// TODO(adlr): detect network failures
+void LibcurlHttpFetcher::CurlPerformOnce() {
+  CHECK(transfer_in_progress_);
+  int running_handles = 0;
+  CURLMcode retcode = CURLM_CALL_MULTI_PERFORM;
+
+  // libcurl may request that we immediately call curl_multi_perform after it
+  // returns, so we do. libcurl promises that curl_multi_perform will not block.
+  while (CURLM_CALL_MULTI_PERFORM == retcode) {
+    retcode = curl_multi_perform(curl_multi_handle_, &running_handles);
+  }
+  if (0 == running_handles) {
+    // we're done!
+    CleanUp();
+    if (delegate_)
+      delegate_->TransferComplete(this, true);  // success
+  } else {
+    // set up callback
+    SetupMainloopSources();
+  }
+}
+
+size_t LibcurlHttpFetcher::LibcurlWrite(void *ptr, size_t size, size_t nmemb) {
+  if (delegate_)
+    delegate_->ReceivedBytes(this, reinterpret_cast<char*>(ptr), size * nmemb);
+  return size * nmemb;
+}
+
+void LibcurlHttpFetcher::Pause() {
+  CHECK(curl_handle_);
+  CHECK(transfer_in_progress_);
+  CHECK_EQ(CURLE_OK, curl_easy_pause(curl_handle_, CURLPAUSE_ALL));
+}
+
+void LibcurlHttpFetcher::Unpause() {
+  CHECK(curl_handle_);
+  CHECK(transfer_in_progress_);
+  CHECK_EQ(CURLE_OK, curl_easy_pause(curl_handle_, CURLPAUSE_CONT));
+}
+
+// This method sets up callbacks with the glib main loop.
+void LibcurlHttpFetcher::SetupMainloopSources() {
+  fd_set fd_read;
+  fd_set fd_write;
+  fd_set fd_exec;
+
+  FD_ZERO(&fd_read);
+  FD_ZERO(&fd_write);
+  FD_ZERO(&fd_exec);
+
+  int fd_max = 0;
+
+  // Ask libcurl for the set of file descriptors we should track on its
+  // behalf.
+  CHECK_EQ(CURLM_OK, curl_multi_fdset(curl_multi_handle_, &fd_read, &fd_write,
+                                      &fd_exec, &fd_max));
+
+  // We should iterate through all file descriptors up to libcurl's fd_max or
+  // the highest one we're tracking, whichever is larger
+  if (!io_channels_.empty())
+    fd_max = max(fd_max, io_channels_.rbegin()->first);
+
+  // For each fd, if we're not tracking it, track it. If we are tracking it,
+  // but libcurl doesn't care about it anymore, stop tracking it.
+  // After this loop, there should be exactly as many GIOChannel objects
+  // in io_channels_ as there are fds that we're tracking.
+  for (int i = 0; i <= fd_max; i++) {
+    if (!(FD_ISSET(i, &fd_read) || FD_ISSET(i, &fd_write) ||
+        FD_ISSET(i, &fd_exec))) {
+      // if we have an outstanding io_channel, remove it
+      if (io_channels_.find(i) != io_channels_.end()) {
+        g_source_remove(io_channels_[i].second);
+        g_io_channel_unref(io_channels_[i].first);
+        io_channels_.erase(io_channels_.find(i));
+      }
+      continue;
+    }
+    // If we are already tracking this fd, continue.
+    if (io_channels_.find(i) != io_channels_.end())
+      continue;
+
+    // We must track a new fd
+    GIOChannel *io_channel = g_io_channel_unix_new(i);
+    guint tag = g_io_add_watch(
+        io_channel,
+        static_cast<GIOCondition>(G_IO_IN | G_IO_OUT | G_IO_PRI |
+                                  G_IO_ERR | G_IO_HUP),
+        &StaticFDCallback,
+        this);
+    io_channels_[i] = make_pair(io_channel, tag);
+  }
+
+  // Wet up a timeout callback for libcurl
+  long ms = 0;
+  CHECK_EQ(CURLM_OK, curl_multi_timeout(curl_multi_handle_, &ms));
+  if (ms < 0) {
+    // From http://curl.haxx.se/libcurl/c/curl_multi_timeout.html:
+    //     if libcurl returns a -1 timeout here, it just means that libcurl
+    //     currently has no stored timeout value. You must not wait too long
+    //     (more than a few seconds perhaps) before you call
+    //     curl_multi_perform() again.
+    ms = idle_ms_;
+  }
+  if (timeout_source_) {
+    g_source_destroy(timeout_source_);
+    timeout_source_ = NULL;
+  }
+  timeout_source_ = g_timeout_source_new(ms);
+  CHECK(timeout_source_);
+  g_source_set_callback(timeout_source_, StaticTimeoutCallback, this,
+                        NULL);
+  g_source_attach(timeout_source_, NULL);
+}
+
+bool LibcurlHttpFetcher::FDCallback(GIOChannel *source,
+                                    GIOCondition condition) {
+  // Figure out which source it was; hopefully there aren't too many b/c
+  // this is a linear scan of our channels
+  bool found_in_set = false;
+  for (IOChannels::iterator it = io_channels_.begin();
+       it != io_channels_.end(); ++it) {
+    if (it->second.first == source) {
+      // We will return false from this method, meaning that we shouldn't keep
+      // this g_io_channel around. So we remove it now from our collection of
+      // g_io_channels so that the other code in this class doens't mess with
+      // this (doomed) GIOChannel.
+      // TODO(adlr): optimize by seeing if we should reuse this GIOChannel
+      g_source_remove(it->second.second);
+      g_io_channel_unref(it->second.first);
+      io_channels_.erase(it);
+      found_in_set = true;
+      break;
+    }
+  }
+  CHECK(found_in_set);
+  CurlPerformOnce();
+  return false;
+}
+
+bool LibcurlHttpFetcher::TimeoutCallback() {
+  // Since we will return false from this function, which tells glib to
+  // destroy the timeout callback, we must NULL it out here. This way, when
+  // setting up callback sources again, we won't try to delete this (doomed)
+  // timeout callback then.
+  // TODO(adlr): optimize by checking if we can keep this timeout callback.
+  timeout_source_ = NULL;
+  CurlPerformOnce();
+  return false;
+}
+
+void LibcurlHttpFetcher::CleanUp() {
+  if (timeout_source_) {
+    g_source_destroy(timeout_source_);
+    timeout_source_ = NULL;
+  }
+
+  for (IOChannels::iterator it = io_channels_.begin();
+       it != io_channels_.end(); ++it) {
+    g_source_remove(it->second.second);
+    g_io_channel_unref(it->second.first);
+  }
+  io_channels_.clear();
+
+  if (curl_handle_) {
+    if (curl_multi_handle_) {
+      CHECK_EQ(CURLM_OK,
+               curl_multi_remove_handle(curl_multi_handle_, curl_handle_));
+    }
+    curl_easy_cleanup(curl_handle_);
+    curl_handle_ = NULL;
+  }
+  if (curl_multi_handle_) {
+    CHECK_EQ(CURLM_OK, curl_multi_cleanup(curl_multi_handle_));
+    curl_multi_handle_ = NULL;
+  }
+  transfer_in_progress_ = false;
+}
+
+}  // namespace chromeos_update_engine