diff --git a/Android.mk b/Android.mk index 2311998a62..301d6ab62e 100644 --- a/Android.mk +++ b/Android.mk @@ -42,6 +42,7 @@ LOCAL_SRC_FILES :=\ midi/midi_input.cpp \ net/http_client.cpp \ net/resolve.cpp \ + net/url.cpp \ profiler/profiler.cpp \ thread/threadutil.cpp \ thread/prioritizedworkqueue.cpp \ diff --git a/android/.classpath b/android/.classpath index a4763d1eec..bb0c759770 100644 --- a/android/.classpath +++ b/android/.classpath @@ -4,5 +4,6 @@ + diff --git a/base/buffer.cpp b/base/buffer.cpp index 9abcfe8eab..55b9e11015 100644 --- a/base/buffer.cpp +++ b/base/buffer.cpp @@ -76,7 +76,7 @@ int Buffer::SkipLineCRLF() { } int Buffer::OffsetToAfterNextCRLF() { - for (size_t i = 0; i < data_.size() - 1; i++) { + for (int i = 0; i < (int)data_.size() - 1; i++) { if (data_[i] == '\r' && data_[i + 1] == '\n') { return i + 2; } @@ -109,6 +109,17 @@ bool Buffer::Flush(int fd) { return success; } +bool Buffer::FlushToFile(const char *filename) { + FILE *f = fopen(filename, "wb"); + if (!f) + return false; + if (data_.size()) { + fwrite(&data_[0], 1, data_.size(), f); + } + fclose(f); + return true; +} + bool Buffer::FlushSocket(uintptr_t sock) { for (size_t pos = 0, end = data_.size(); pos < end; ) { int sent = send(sock, &data_[pos], end - pos, 0); @@ -132,13 +143,20 @@ bool Buffer::FlushSocket(uintptr_t sock) { return true; } -void Buffer::ReadAll(int fd) { - char buf[1024]; - int retval; - while ((retval = recv(fd, buf, sizeof(buf), 0)) > 0) { - char *p = Append((size_t)retval); - memcpy(p, buf, retval); - } +bool Buffer::ReadAll(int fd) { + char buf[1024]; + while (true) { + int retval = recv(fd, buf, sizeof(buf), 0); + if (retval == 0) + return true; + else if (retval < 0) { + ELOG("Error reading from buffer: %i", retval); + return false; + } + char *p = Append((size_t)retval); + memcpy(p, buf, retval); + } + return true; } void Buffer::Read(int fd, size_t sz) { diff --git a/base/buffer.h b/base/buffer.h index 5f716edbb6..440eb5015f 100644 --- a/base/buffer.h +++ b/base/buffer.h @@ -60,10 +60,11 @@ class Buffer { // Writes the entire buffer to the file descriptor. Also resets the // size to zero. On failure, data remains in buffer and nothing is // written. - bool Flush(int fd); + bool Flush(int fd); + bool FlushToFile(const char *filename); bool FlushSocket(uintptr_t sock); // Windows portability - void ReadAll(int fd); + bool ReadAll(int fd); void Read(int fd, size_t sz); // Utilities. Try to avoid checking for size. diff --git a/base/functional.h b/base/functional.h new file mode 100644 index 0000000000..46c516f941 --- /dev/null +++ b/base/functional.h @@ -0,0 +1,24 @@ +// This file simply includes and applies any necessary compatibility fixes for +// strange platforms like iOS. + +// Use p as the namespace for placeholders. + +#pragma once + +#include + +#if defined(IOS) || defined(MACGNUSTD) +#include +namespace std { + using tr1::bind; +} +#endif + +#ifdef __SYMBIAN32__ +#define p +#elif defined(IOS) +#include +namespace p = std::tr1::placeholders; +#else +namespace p = std::placeholders; +#endif diff --git a/input/input_state.h b/input/input_state.h index 37bacd460e..553bafb810 100644 --- a/input/input_state.h +++ b/input/input_state.h @@ -29,6 +29,10 @@ enum { PAD_BUTTON_LEFT_THUMB = 1 << 18, // Click left thumb stick on X360 PAD_BUTTON_RIGHT_THUMB = 1 << 19, // Click right thumb stick on X360 + + PAD_BUTTON_LEFT_TRIGGER = 1 << 21, // Click left thumb stick on X360 + PAD_BUTTON_RIGHT_TRIGGER = 1 << 22, // Click left thumb stick on X360 + PAD_BUTTON_UNTHROTTLE = 1 << 20, // Click Tab to unthrottle }; diff --git a/native.vcxproj b/native.vcxproj index 805dff1e02..83d833ed9a 100644 --- a/native.vcxproj +++ b/native.vcxproj @@ -174,6 +174,7 @@ + @@ -227,6 +228,7 @@ + @@ -342,6 +344,7 @@ + diff --git a/native.vcxproj.filters b/native.vcxproj.filters index da9f3d708f..d4dee4c0f5 100644 --- a/native.vcxproj.filters +++ b/native.vcxproj.filters @@ -254,6 +254,12 @@ thread + + base + + + net + @@ -450,6 +456,9 @@ thread + + net + diff --git a/net/http_client.cpp b/net/http_client.cpp index 739546af5d..90aec31764 100644 --- a/net/http_client.cpp +++ b/net/http_client.cpp @@ -18,6 +18,9 @@ #include "base/buffer.h" #include "base/stringutil.h" #include "net/resolve.h" +#include "net/url.h" +#include "base/functional.h" + // #include "strings/strutil.h" namespace net { @@ -99,24 +102,30 @@ namespace http { Client::Client() { } Client::~Client() { + Disconnect(); } #define USERAGENT "METAGET 1.0" -void Client::GET(const char *resource, Buffer *output) { +int Client::GET(const char *resource, Buffer *output) { Buffer buffer; const char *tpl = "GET %s HTTP/1.0\r\nHost: %s\r\nUser-Agent: " USERAGENT "\r\n\r\n"; buffer.Printf(tpl, resource, host_.c_str()); - CHECK(buffer.FlushSocket(sock())); + bool flushed = buffer.FlushSocket(sock()); + if (!flushed) { + return -1; // TODO error code. + } // Snarf all the data we can. - output->ReadAll(sock()); + if (!output->ReadAll(sock())) + return -1; - // Skip the header. + // Skip the header. TODO: read HTTP code and file size so we can make progress bars. while (output->SkipLineCRLF() > 0) ; // output now contains the rest of the reply. + return 200; } int Client::POST(const char *resource, const std::string &data, const std::string &mime, Buffer *output) { @@ -148,7 +157,7 @@ int Client::POST(const char *resource, const std::string &data, const std::strin // Tear off the http headers, leaving the actual response data. std::string firstline; CHECK_GT(output->TakeLineCRLF(&firstline), 0); - int code = atoi(&firstline[9]); // ugggly hardcoding + int code = atoi(&firstline[9]); //VLOG(1) << "HTTP result code: " << code; while (true) { int skipped = output->SkipLineCRLF(); @@ -163,4 +172,60 @@ int Client::POST(const char *resource, const std::string &data, Buffer *output) return POST(resource, data, "", output); } +Download::Download(const std::string &url, const std::string &outfile) + : url_(url), outfile_(outfile), progress_(0.0f), failed_(false) { + + std::thread th(std::bind(&Download::Do, this)); + th.detach(); +} + +Download::~Download() { + +} + + +void Download::Do() { + Url fileUrl(url_); + if (!fileUrl.Valid()) { + failed_ = true; + return; + } + net::Init(); + + http::Client client; + if (!client.Resolve(fileUrl.Host().c_str(), 80)) { + ELOG("Failed resolving %s", url_.c_str()); + failed_ = true; + return; + } + client.Connect(); + if (client.GET(fileUrl.Resource().c_str(), &buffer_)) { + progress_ = 1.0f; + ILOG("Completed downloading %s to %s", url_.c_str(), outfile_.c_str()); + if (!buffer_.FlushToFile(outfile_.c_str())) { + ELOG("Failed writing download to %s", outfile_.c_str()); + } + } else { + ELOG("Error downloading %s to %s", url_.c_str(), outfile_.c_str()); + } + + net::Shutdown(); +} + +std::shared_ptr Downloader::StartDownload(const std::string &url, const std::string &outfile) { + std::shared_ptr dl(new Download(url, outfile)); + downloads_.push_back(dl); + return dl; +} + +void Downloader::Update() { + restart: + for (size_t i = 0; i < downloads_.size(); i++) { + if (downloads_[i]->Progress() == 1.0f || downloads_[i]->Failed()) { + downloads_.erase(downloads_.begin() + i); + goto restart; + } + } +} + } // http diff --git a/net/http_client.h b/net/http_client.h index 2b36360541..9de4e64f1a 100644 --- a/net/http_client.h +++ b/net/http_client.h @@ -3,6 +3,7 @@ #include "base/basictypes.h" #include "base/buffer.h" +#include "thread/thread.h" #ifdef _WIN32 #include @@ -56,7 +57,8 @@ public: Client(); ~Client(); - void GET(const char *resource, Buffer *output); + // Return value is the HTTP return code. 200 means OK. < 0 means some local error. + int GET(const char *resource, Buffer *output); // Return value is the HTTP return code. int POST(const char *resource, const std::string &data, const std::string &mime, Buffer *output); @@ -65,6 +67,41 @@ public: // HEAD, PUT, DELETE aren't implemented yet. }; +// Not particularly efficient, but hey - it's a background download, that's pretty cool :P +class Download { +public: + Download(const std::string &url, const std::string &outfile); + ~Download(); + + // Returns 1.0 when done. That one value can be compared exactly. + float Progress() const { return progress_; } + bool Failed() const { return failed_; } + + std::string url() const { return url_; } + std::string outfile() const { return outfile_; } + +private: + void Do(); // Actually does the download. Runs on thread. + + float progress_; + Buffer buffer_; + std::string url_; + std::string outfile_; + bool failed_; +}; + +class Downloader { +public: + std::shared_ptr StartDownload(const std::string &url, const std::string &outfile); + + // Drops finished downloads from the list. + void Update(); + +private: + std::vector> downloads_; +}; + + } // http #endif // _NET_HTTP_HTTP_CLIENT diff --git a/net/resolve.cpp b/net/resolve.cpp index 6b0af606f0..79d0162096 100644 --- a/net/resolve.cpp +++ b/net/resolve.cpp @@ -22,6 +22,7 @@ namespace net { void Init() { #ifdef _WIN32 + // WSA does its own internal reference counting, no need to keep track of if we inited or not. WSADATA wsaData = {0}; WSAStartup(MAKEWORD(2, 2), &wsaData); #endif diff --git a/net/url.cpp b/net/url.cpp new file mode 100644 index 0000000000..7b788260ee --- /dev/null +++ b/net/url.cpp @@ -0,0 +1,22 @@ +#include "base/logging.h" +#include "net/url.h" + +const char *UrlEncoder::unreservedChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~"; +const char *UrlEncoder::hexChars = "0123456789ABCDEF"; + +void Url::Split() { + size_t colonSlashSlash = url_.find("://"); + if (colonSlashSlash == std::string::npos) { + ELOG("Invalid URL: %s", url_.c_str()); + return; + } + + protocol_ = url_.substr(0, colonSlashSlash); + + size_t sep = url_.find('/', colonSlashSlash + 3); + + host_ = url_.substr(colonSlashSlash + 3, sep - colonSlashSlash - 3); + resource_ = url_.substr(sep); // include the slash! + + valid_ = protocol_.size() > 1 && host_.size() > 1; +} \ No newline at end of file diff --git a/net/url.h b/net/url.h new file mode 100644 index 0000000000..2358dfb446 --- /dev/null +++ b/net/url.h @@ -0,0 +1,126 @@ +#pragma once + +#include +#include +#include +#include + +#include "base/basictypes.h" + +#if defined(_MSC_VER) +#undef snprintf +#define snprintf _snprintf +#endif + +struct UrlEncoder +{ + UrlEncoder() : paramCount(0) + { + data.reserve(256); + }; + + void Add(const std::string &key, const std::string &value) + { + if (++paramCount > 1) + data += '&'; + AppendEscaped(key); + data += '='; + AppendEscaped(value); + } + + void Add(const std::string &key, const char *value) + { + Add(key, std::string(value)); + } + + template + void AddT(const std::string &key, const char *fmt, const T value) + { + char temp[64]; + snprintf(temp, sizeof(temp), fmt, value); + temp[sizeof(temp) - 1] = '\0'; + Add(key, temp); + } + + void Add(const std::string &key, const int value) + { + AddT(key, "%d", value); + } + + void Add(const std::string &key, const uint32_t value) + { + AddT(key, "%u", value); + } + + void Add(const std::string &key, const uint64_t value) + { + AddT(key, "%llu", value); + } + + void Add(const std::string &key, const double value) + { + AddT(key, "%f", value); + } + + void Add(const std::string &key, const bool value) + { + Add(key, value ? "true" : "false"); + } + + // Percent encoding, aka application/x-www-form-urlencoded. + void AppendEscaped(const std::string &value) + { + for (size_t lastEnd = 0; lastEnd < value.length(); ) + { + size_t pos = value.find_first_not_of(unreservedChars, lastEnd); + if (pos == value.npos) + { + data += value.substr(lastEnd); + break; + } + + if (pos != lastEnd) + data += value.substr(lastEnd, pos - lastEnd); + lastEnd = pos; + + // Encode the reserved character. + char c = value[pos]; + data += '%'; + data += hexChars[(c >> 4) & 15]; + data += hexChars[(c >> 0) & 15]; + ++lastEnd; + } + } + + std::string &ToString() + { + return data; + } + + std::string data; + int paramCount; + static const char *unreservedChars; + static const char *hexChars; +}; + + +class Url { +public: + Url(const std::string url) : url_(url), valid_(false) { + Split(); + } + + bool Valid() const { return valid_; } + + std::string Host() const { return host_; } + std::string Protocol() const { return protocol_; } + std::string Resource() const { return resource_; } + +private: + void Split(); + bool valid_; + std::string url_; + std::string host_; + std::string resource_; + std::string protocol_; +}; \ No newline at end of file diff --git a/thread/prioritizedworkqueue.cpp b/thread/prioritizedworkqueue.cpp index 746c3fb3c6..18369ed617 100644 --- a/thread/prioritizedworkqueue.cpp +++ b/thread/prioritizedworkqueue.cpp @@ -1,16 +1,8 @@ -#include - +#include "base/functional.h" #include "base/logging.h" #include "thread/thread.h" #include "thread/prioritizedworkqueue.h" -#if defined(IOS) || defined(MACGNUSTD) -#include -namespace std { - using tr1::bind; -} -#endif - PrioritizedWorkQueue::~PrioritizedWorkQueue() { if (!done_) { ELOG("PrioritizedWorkQueue destroyed but not done!");