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!");