paulxstretch/deps/juce/extras/Projucer/Source/Application/UserAccount/jucer_LicenseQueryThread.h

372 lines
14 KiB
C
Raw Permalink Normal View History

/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
namespace LicenseHelpers
{
inline LicenseState::Type licenseTypeForString (const String& licenseString)
{
if (licenseString == "juce-pro") return LicenseState::Type::pro;
if (licenseString == "juce-indie") return LicenseState::Type::indie;
if (licenseString == "juce-edu") return LicenseState::Type::educational;
if (licenseString == "juce-personal") return LicenseState::Type::personal;
jassertfalse; // unknown type
return LicenseState::Type::none;
}
using LicenseVersionAndType = std::pair<int, LicenseState::Type>;
inline LicenseVersionAndType findBestLicense (std::vector<LicenseVersionAndType>&& licenses)
{
if (licenses.size() == 1)
return licenses[0];
auto getValueForLicenceType = [] (LicenseState::Type type)
{
switch (type)
{
case LicenseState::Type::pro: return 4;
case LicenseState::Type::indie: return 3;
case LicenseState::Type::educational: return 2;
case LicenseState::Type::personal: return 1;
case LicenseState::Type::gpl:
case LicenseState::Type::none:
default: return -1;
}
};
std::sort (licenses.begin(), licenses.end(),
[getValueForLicenceType] (const LicenseVersionAndType& l1, const LicenseVersionAndType& l2)
{
if (l1.first > l2.first)
return true;
if (l1.first == l2.first)
return getValueForLicenceType (l1.second) > getValueForLicenceType (l2.second);
return false;
});
auto findFirstLicense = [&licenses] (bool isPaid)
{
auto iter = std::find_if (licenses.begin(), licenses.end(),
[isPaid] (const LicenseVersionAndType& l)
{
auto proOrIndie = (l.second == LicenseState::Type::pro || l.second == LicenseState::Type::indie);
return isPaid ? proOrIndie : ! proOrIndie;
});
return iter != licenses.end() ? *iter
: LicenseVersionAndType();
};
auto newestPaid = findFirstLicense (true);
auto newestFree = findFirstLicense (false);
if (newestPaid.first >= projucerMajorVersion || newestPaid.first >= newestFree.first)
return newestPaid;
return newestFree;
}
}
//==============================================================================
class LicenseQueryThread
{
public:
enum class ErrorType
{
busy,
cancelled,
connectionError,
webResponseError
};
using ErrorMessageAndType = std::pair<String, ErrorType>;
using LicenseQueryCallback = std::function<void (ErrorMessageAndType, LicenseState)>;
//==============================================================================
LicenseQueryThread() = default;
void checkLicenseValidity (const LicenseState& state, LicenseQueryCallback completionCallback)
{
if (jobPool.getNumJobs() > 0)
{
completionCallback ({ {}, ErrorType::busy }, {});
return;
}
jobPool.addJob ([this, state, completionCallback]
{
auto updatedState = state;
auto result = runTask (std::make_unique<UserLicenseQuery> (state.authToken), updatedState);
WeakReference<LicenseQueryThread> weakThis (this);
MessageManager::callAsync ([weakThis, result, updatedState, completionCallback]
{
if (weakThis != nullptr)
completionCallback (result, updatedState);
});
});
}
void doSignIn (const String& email, const String& password, LicenseQueryCallback completionCallback)
{
cancelRunningJobs();
jobPool.addJob ([this, email, password, completionCallback]
{
LicenseState state;
auto result = runTask (std::make_unique<UserLogin> (email, password), state);
if (result == ErrorMessageAndType())
result = runTask (std::make_unique<UserLicenseQuery> (state.authToken), state);
if (result != ErrorMessageAndType())
state = {};
WeakReference<LicenseQueryThread> weakThis (this);
MessageManager::callAsync ([weakThis, result, state, completionCallback]
{
if (weakThis != nullptr)
completionCallback (result, state);
});
});
}
void cancelRunningJobs()
{
jobPool.removeAllJobs (true, 500);
}
private:
//==============================================================================
struct AccountEnquiryBase
{
virtual ~AccountEnquiryBase() = default;
virtual bool isPOSTLikeRequest() const = 0;
virtual String getEndpointURLSuffix() const = 0;
virtual StringPairArray getParameterNamesAndValues() const = 0;
virtual String getExtraHeaders() const = 0;
virtual int getSuccessCode() const = 0;
virtual String errorCodeToString (int) const = 0;
virtual bool parseServerResponse (const String&, LicenseState&) = 0;
};
struct UserLogin : public AccountEnquiryBase
{
UserLogin (const String& e, const String& p)
: userEmail (e), userPassword (p)
{
}
bool isPOSTLikeRequest() const override { return true; }
String getEndpointURLSuffix() const override { return "/authenticate/projucer"; }
int getSuccessCode() const override { return 200; }
StringPairArray getParameterNamesAndValues() const override
{
StringPairArray namesAndValues;
namesAndValues.set ("email", userEmail);
namesAndValues.set ("password", userPassword);
return namesAndValues;
}
String getExtraHeaders() const override
{
return "Content-Type: application/json";
}
String errorCodeToString (int errorCode) const override
{
switch (errorCode)
{
case 400: return "Please enter your email and password to sign in.";
case 401: return "Your email and password are incorrect.";
case 451: return "Access denied.";
default: return "Something went wrong, please try again.";
}
}
bool parseServerResponse (const String& serverResponse, LicenseState& licenseState) override
{
auto json = JSON::parse (serverResponse);
licenseState.authToken = json.getProperty ("token", {}).toString();
licenseState.username = json.getProperty ("user", {}).getProperty ("username", {}).toString();
return (licenseState.authToken.isNotEmpty() && licenseState.username.isNotEmpty());
}
String userEmail, userPassword;
};
struct UserLicenseQuery : public AccountEnquiryBase
{
UserLicenseQuery (const String& authToken)
: userAuthToken (authToken)
{
}
bool isPOSTLikeRequest() const override { return false; }
String getEndpointURLSuffix() const override { return "/user/licences/projucer"; }
int getSuccessCode() const override { return 200; }
StringPairArray getParameterNamesAndValues() const override
{
return {};
}
String getExtraHeaders() const override
{
return "x-access-token: " + userAuthToken;
}
String errorCodeToString (int errorCode) const override
{
switch (errorCode)
{
case 401: return "User not found or could not be verified.";
default: return "User licenses info fetch failed (unknown error).";
}
}
bool parseServerResponse (const String& serverResponse, LicenseState& licenseState) override
{
auto json = JSON::parse (serverResponse);
if (auto* licensesJson = json.getArray())
{
std::vector<LicenseHelpers::LicenseVersionAndType> licenses;
for (auto& license : *licensesJson)
{
auto version = license.getProperty ("product_version", {}).toString().trim();
auto type = license.getProperty ("licence_type", {}).toString();
auto status = license.getProperty ("status", {}).toString();
if (status == "active" && type.isNotEmpty() && version.isNotEmpty())
licenses.push_back ({ version.getIntValue(), LicenseHelpers::licenseTypeForString (type) });
}
if (! licenses.empty())
{
auto bestLicense = LicenseHelpers::findBestLicense (std::move (licenses));
licenseState.version = bestLicense.first;
licenseState.type = bestLicense.second;
}
return true;
}
return false;
}
String userAuthToken;
};
//==============================================================================
static String postDataStringAsJSON (const StringPairArray& parameters)
{
DynamicObject::Ptr d (new DynamicObject());
for (auto& key : parameters.getAllKeys())
d->setProperty (key, parameters[key]);
return JSON::toString (var (d.get()));
}
static ErrorMessageAndType runTask (std::unique_ptr<AccountEnquiryBase> accountEnquiryTask, LicenseState& state)
{
const ErrorMessageAndType cancelledError ("Cancelled.", ErrorType::cancelled);
const String endpointURL ("https://api.juce.com/api/v1");
URL url (endpointURL + accountEnquiryTask->getEndpointURLSuffix());
auto isPOST = accountEnquiryTask->isPOSTLikeRequest();
if (isPOST)
url = url.withPOSTData (postDataStringAsJSON (accountEnquiryTask->getParameterNamesAndValues()));
if (ThreadPoolJob::getCurrentThreadPoolJob()->shouldExit())
return cancelledError;
int statusCode = 0;
auto urlStream = url.createInputStream (URL::InputStreamOptions (isPOST ? URL::ParameterHandling::inPostData
: URL::ParameterHandling::inAddress)
.withExtraHeaders (accountEnquiryTask->getExtraHeaders())
.withConnectionTimeoutMs (5000)
.withStatusCode (&statusCode));
if (urlStream == nullptr)
return { "Failed to connect to the web server.", ErrorType::connectionError };
if (statusCode != accountEnquiryTask->getSuccessCode())
return { accountEnquiryTask->errorCodeToString (statusCode), ErrorType::webResponseError };
if (ThreadPoolJob::getCurrentThreadPoolJob()->shouldExit())
return cancelledError;
String response;
for (;;)
{
char buffer [8192] = "";
auto num = urlStream->read (buffer, sizeof (buffer));
if (ThreadPoolJob::getCurrentThreadPoolJob()->shouldExit())
return cancelledError;
if (num <= 0)
break;
response += buffer;
}
if (ThreadPoolJob::getCurrentThreadPoolJob()->shouldExit())
return cancelledError;
if (! accountEnquiryTask->parseServerResponse (response, state))
return { "Failed to parse server response.", ErrorType::webResponseError };
return {};
}
//==============================================================================
ThreadPool jobPool { 1 };
//==============================================================================
JUCE_DECLARE_WEAK_REFERENCEABLE (LicenseQueryThread)
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LicenseQueryThread)
};