I integrated a number of suggestions by @ratchet freak and @Adriano Repetti:
- I switched to automatic closing of handles, RAII-style.
- I added logic to handle
ERROR_INSUFFICIENT_BUFFER
errors returned by InternetReadFile()
.
- I added proper error handling when navigating the JSON response.
- I added the
INTERNET_FLAG_NO_UI
flag to the HttpOpenRequest
call.
- I moved a couple of magic strings to constants.
Regarding the use of std::put_time()
and std::get_time()
(which I considered when I wrote the initial code), what put me off is that, as far as I can tell, they don't actually work with strings, but with streams. I find that this introduces boilerplate without bringing much tangible benefits.
Regarding InternetGetLastResponseInfo()
and the (good) suggestions to introduce a github
class: I'm not willing to do too much error handling or formalization as this code is simply meant to retrieve whether a newer update of the product is available (in which case it should print a few information about the new release like the version, the publication date and maybe a download link). That's why I'm trying to keep this code as focused and straightforward as possible, until further needs arise. (But of course it should not crash the application, hence the proper error handling.)
Here is the revised code:
// Code in public domain, do whatever you want with it.
#include "rapidjson/document.h"
#include <Windows.h>
#include <wininet.h>
#include <tchar.h>
#include <cstddef>
#include <ctime>
#include <exception>
#include <iostream>
#include <string>
#include <vector>
namespace json = rapidjson;
class HInternet
{
public:
HInternet(const HINTERNET handle)
: m_handle(handle)
{
}
~HInternet()
{
if (m_handle != nullptr)
InternetCloseHandle(m_handle);
}
operator HINTERNET() const
{
return m_handle;
}
private:
const HINTERNET m_handle;
};
DWORD query_release_information(std::string& response)
{
static const TCHAR* Host = _T("api.github.com");
static const TCHAR* Path = _T("/repos/appleseedhq/appleseed-max/releases");
response.clear();
const HInternet session =
InternetOpen(
_T("appleseed"),
INTERNET_OPEN_TYPE_DIRECT,
nullptr,
nullptr,
0);
if (!session)
return GetLastError();
const HInternet connection =
InternetConnect(
session,
Host,
INTERNET_DEFAULT_HTTPS_PORT,
nullptr,
nullptr,
INTERNET_SERVICE_HTTP,
0,
0);
if (!connection)
return GetLastError();
static const TCHAR* AcceptTypes[] = { _T("application/json"), nullptr };
const HInternet request =
HttpOpenRequest(
connection,
_T("GET"),
Path,
nullptr,
nullptr,
AcceptTypes,
INTERNET_FLAG_SECURE | INTERNET_FLAG_NO_UI | INTERNET_FLAG_RELOAD,
0);
if (!request)
return GetLastError();
if (!HttpSendRequest(request, nullptr, 0, nullptr, 0))
return GetLastError();
std::vector<char> buffer(16);
while (true)
{
DWORD bytes_read;
if (!InternetReadFile(request, &buffer[0], buffer.size(), &bytes_read))
{
const DWORD last_error = GetLastError();
if (last_error == ERROR_INSUFFICIENT_BUFFER)
buffer.resize(buffer.size() * 2);
else return last_error;
}
if (bytes_read > 0)
response.append(&buffer[0], bytes_read);
else return ERROR_SUCCESS;
}
}
struct JSONMemberNotFound : public std::exception {};
const json::Value& get_member(const json::Value& parent, const json::Value::Ch* member)
{
if (!parent.HasMember(member))
throw JSONMemberNotFound();
return parent[member];
}
const json::Value& get_index(const json::Value& parent, const std::size_t index)
{
if (!parent.IsArray() || index >= parent.Size())
throw JSONMemberNotFound();
return parent[index];
}
int main()
{
std::string json;
if (query_release_information(json) != ERROR_SUCCESS)
return -1;
json::Document doc;
if (doc.Parse(json.c_str()).HasParseError())
return -1;
try
{
const auto& release = get_index(doc, 0);
const auto& tag_name = get_member(release, "tag_name");
const auto& published_date = get_member(release, "published_at");
const auto& assets = get_member(release, "assets");
const auto& asset = get_index(assets, 0);
const auto& asset_download_url = get_member(asset, "browser_download_url");
int year, month, day, hours, minutes, seconds;
std::sscanf(
published_date.GetString(),
"%d-%d-%dT%d:%d:%d:%dZ",
&year, &month, &day, &hours, &minutes, &seconds);
std::tm time;
time.tm_year = year - 1900;
time.tm_mon = month - 1;
time.tm_mday = day;
time.tm_hour = hours;
time.tm_min = minutes;
time.tm_sec = seconds;
const std::time_t t = std::mktime(&time);
const std::tm* local_time = std::localtime(&t);
char buffer[1024];
std::strftime(buffer, sizeof(buffer), "%b %d %Y", local_time);
std::cout << buffer << std::endl;
return 0;
}
catch (const JSONMemberNotFound&)
{
return -1;
}
}