1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00
JUCE/extras/Projucer/Source/Licenses/jucer_LicenseThread.h

458 lines
17 KiB
C++

/*
==============================================================================
This file is part of the JUCE 6 technical preview.
Copyright (c) 2017 - ROLI Ltd.
You may use this code under the terms of the GPL v3
(see www.gnu.org/licenses).
For this technical preview, this file is not subject to commercial licensing.
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
//==============================================================================
struct NetWorkerThread : public Thread,
private AsyncUpdater
{
NetWorkerThread() : Thread ("License") {}
~NetWorkerThread() override
{
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
signalThreadShouldExit();
cancelPendingUpdate();
finished.signal();
{
ScopedLock lock (weakReferenceLock);
if (currentInputStream != nullptr)
currentInputStream->cancel();
}
waitForThreadToExit (-1);
}
//==============================================================================
void executeOnMessageThreadAndBlock (std::function<void()> f, bool signalWhenFinished = true)
{
// only call this on the worker thread
jassert (Thread::getCurrentThreadId() == getThreadId());
if (! isWaiting)
{
ScopedValueSetter<bool> reentrant (isWaiting, true);
finished.reset();
if (! threadShouldExit())
{
functionToExecute = [signalWhenFinished, f, this] () { f(); if (signalWhenFinished) finished.signal(); };
triggerAsyncUpdate();
finished.wait (-1);
}
}
else
{
// only one task at a time
jassertfalse;
return;
}
}
WebInputStream* getSharedWebInputStream (const URL& url, const bool usePost)
{
ScopedLock lock (weakReferenceLock);
if (threadShouldExit())
return nullptr;
jassert (currentInputStream == nullptr);
return (currentInputStream = new WeakWebInputStream (*this, url, usePost));
}
bool isWaiting = false;
WaitableEvent finished;
private:
//==============================================================================
void handleAsyncUpdate() override
{
if (functionToExecute)
{
std::function<void()> f;
std::swap (f, functionToExecute);
if (! threadShouldExit())
f();
}
}
//==============================================================================
struct WeakWebInputStream : public WebInputStream
{
WeakWebInputStream (NetWorkerThread& workerThread, const URL& url, const bool usePost)
: WebInputStream (url, usePost), owner (workerThread) {}
~WeakWebInputStream()
{
ScopedLock lock (owner.weakReferenceLock);
owner.currentInputStream = nullptr;
}
NetWorkerThread& owner;
WeakReference<WeakWebInputStream>::Master masterReference;
friend class WeakReference<WeakWebInputStream>;
};
//==============================================================================
friend struct WeakWebInputStream;
std::function<void()> functionToExecute;
CriticalSection weakReferenceLock;
WebInputStream* currentInputStream = nullptr;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NetWorkerThread)
};
//==============================================================================
//==============================================================================
//==============================================================================
struct LicenseThread : NetWorkerThread
{
LicenseThread (LicenseController& licenseController, bool shouldSelectNewLicense)
: owner (licenseController), selectNewLicense (shouldSelectNewLicense)
{
startThread();
}
String getAuthToken()
{
if (owner.state.authToken.isNotEmpty())
return owner.state.authToken;
selectNewLicense = false;
HashMap<String, String> result;
if (! queryWebview ("https://auth.roli.com/signin/projucer?redirect=projucer://receive-auth-token?token=",
"receive-auth-token", result))
return {};
return result["token"];
}
// returns true if any information was updated
void updateUserInfo (LicenseState& stateToUpdate)
{
jassert (stateToUpdate.authToken.isNotEmpty());
auto accessTokenHeader = "x-access-token: " + stateToUpdate.authToken;
std::unique_ptr<WebInputStream> shared (getSharedWebInputStream (URL ("https://api.roli.com/api/v1/user"), false));
if (shared != nullptr)
{
const int statusCode = shared->withExtraHeaders (accessTokenHeader).getStatusCode();
if (statusCode == 200)
{
var result = JSON::parse (shared->readEntireStreamAsString());
shared.reset();
auto newState = licenseStateFromJSON (result, stateToUpdate.authToken, stateToUpdate.avatar);
if (newState.type != LicenseState::Type::notLoggedIn)
stateToUpdate = newState;
}
else if (statusCode == 401)
{
selectNewLicense = false;
// un-authorised: token has expired
stateToUpdate = LicenseState();
}
}
}
void updateLicenseType (LicenseState& stateToUpdate)
{
bool requiredWebview = false;
String licenseChooserPage = "https://juce.com/webviews/select_license";
jassert (stateToUpdate.authToken.isNotEmpty());
jassert (stateToUpdate.type != LicenseState::Type::notLoggedIn);
auto accessTokenHeader = "x-access-token: " + stateToUpdate.authToken;
StringArray licenses;
while ((licenses.isEmpty() || selectNewLicense) && ! threadShouldExit())
{
static Identifier licenseTypeIdentifier ("type");
static Identifier licenseStatusIdentifier ("status");
static Identifier projucerLicenseTypeIdentifier ("licence_type");
static Identifier productNameIdentifier ("product_name");
static Identifier licenseIdentifier ("licence");
static Identifier serialIdentifier ("serial_number");
static Identifier versionIdentifier ("product_version");
static Identifier searchInternalIdentifier ("search_internal_id");
if (! selectNewLicense)
{
std::unique_ptr<WebInputStream> shared (getSharedWebInputStream (URL ("https://api.roli.com/api/v1/user/licences?search_internal_id=com.roli.projucer&version=5"),
false));
if (shared == nullptr)
break;
var json = JSON::parse (shared->withExtraHeaders (accessTokenHeader)
.readEntireStreamAsString());
shared.reset();
if (auto* jsonLicenses = json.getArray())
{
for (auto& v : *jsonLicenses)
{
if (auto* obj = v.getDynamicObject())
{
const String& productType = obj->getProperty (projucerLicenseTypeIdentifier);
const String& status = obj->getProperty (licenseStatusIdentifier);
if (productType.isNotEmpty() && (status.isEmpty() || status == "active"))
licenses.add (productType);
}
}
}
else
{
// no internet -> then use the last valid license
if (stateToUpdate.type != LicenseState::Type::notLoggedIn
&& stateToUpdate.type != LicenseState::Type::noLicenseChosenYet)
return;
}
if (! licenses.isEmpty())
break;
}
// ask the user to select a license
HashMap<String, String> result;
requiredWebview = true;
if (! queryWebview (licenseChooserPage, {}, result))
break;
const String& redirectURL = result["page-redirect"];
const String& productKey = result["register-product"];
const String& chosenLicenseType = result["redeem-licence-type"];
if (redirectURL.isNotEmpty())
{
licenseChooserPage = "https://juce.com/webviews/register-product";
continue;
}
if (productKey.isNotEmpty())
{
DynamicObject::Ptr redeemObject (new DynamicObject());
redeemObject->setProperty (serialIdentifier, productKey);
String postData (JSON::toString (var (redeemObject.get())));
std::unique_ptr<WebInputStream> shared (getSharedWebInputStream (URL ("https://api.roli.com/api/v1/user/products").withPOSTData (postData),
true));
if (shared == nullptr)
break;
int statusCode = shared->withExtraHeaders (accessTokenHeader)
.withExtraHeaders ("Content-Type: application/json")
.getStatusCode();
licenseChooserPage = String ("https://juce.com/webviews/register-product?error=")
+ String (statusCode == 404 ? "invalid" : "server");
if (statusCode == 200)
selectNewLicense = false;
continue;
}
if (chosenLicenseType.isNotEmpty())
{
// redeem the license
DynamicObject::Ptr jsonLicenseObject (new DynamicObject());
jsonLicenseObject->setProperty (projucerLicenseTypeIdentifier, chosenLicenseType);
jsonLicenseObject->setProperty (versionIdentifier, 5);
DynamicObject::Ptr jsonLicenseRequest (new DynamicObject());
jsonLicenseRequest->setProperty (licenseIdentifier, var (jsonLicenseObject.get()));
jsonLicenseRequest->setProperty (searchInternalIdentifier, "com.roli.projucer");
jsonLicenseRequest->setProperty (licenseTypeIdentifier, "software");
String postData (JSON::toString (var (jsonLicenseRequest.get())));
std::unique_ptr<WebInputStream> shared (getSharedWebInputStream (URL ("https://api.roli.com/api/v1/user/products/redeem")
.withPOSTData (postData),
true));
if (shared != nullptr)
{
int statusCode = shared->withExtraHeaders (accessTokenHeader)
.withExtraHeaders ("Content-Type: application/json")
.getStatusCode();
if (statusCode == 200)
selectNewLicense = false;
continue;
}
}
break;
}
HashMap<String, String> result;
if (requiredWebview && ! threadShouldExit())
queryWebview ("https://juce.com/webviews/registration-complete", "licence_provisioned", result);
stateToUpdate.type = getBestLicenseTypeFromLicenses (licenses);
}
//==============================================================================
void run() override
{
LicenseState workState (owner.state);
while (! threadShouldExit())
{
workState.authToken = getAuthToken();
if (workState.authToken.isEmpty())
return;
// read the user information
updateUserInfo (workState);
if (threadShouldExit())
return;
updateIfChanged (workState);
// if the last step logged us out then retry
if (workState.authToken.isEmpty())
continue;
// check if the license has changed
updateLicenseType (workState);
if (threadShouldExit())
return;
updateIfChanged (workState);
closeWebviewOnMessageThread (0);
finished.wait (60 * 5 * 1000);
}
}
//==============================================================================
LicenseState licenseStateFromJSON (const var& json, const String& authToken, const Image& fallbackAvatar)
{
static Identifier usernameIdentifier ("username");
static Identifier emailIdentifier ("email");
static Identifier avatarURLIdentifier ("avatar_url");
LicenseState result;
if (auto* obj = json.getDynamicObject())
{
result.type = LicenseState::Type::noLicenseChosenYet;
result.username = obj->getProperty (usernameIdentifier);
result.authToken = authToken;
result.email = obj->getProperty (emailIdentifier);
result.avatar = fallbackAvatar;
String avatarURL = obj->getProperty (avatarURLIdentifier);
if (avatarURL.isNotEmpty())
{
std::unique_ptr<WebInputStream> shared (getSharedWebInputStream (URL (avatarURL), false));
if (shared != nullptr)
{
MemoryBlock mb;
shared->readIntoMemoryBlock (mb);
result.avatar = ImageFileFormat::loadFrom (mb.getData(), mb.getSize());
}
}
}
return result;
}
//==============================================================================
bool queryWebview (const String& startURL, const String& valueToQuery, HashMap<String, String>& result)
{
executeOnMessageThreadAndBlock ([&] () { owner.queryWebview (startURL, valueToQuery, result); }, false);
return (! threadShouldExit());
}
void closeWebviewOnMessageThread (int result)
{
executeOnMessageThreadAndBlock ([this, result] () { owner.closeWebview (result); });
}
static bool stringArrayContainsSubstring (const StringArray& stringArray, const String& substring)
{
jassert (substring.isNotEmpty());
for (auto element : stringArray)
if (element.containsIgnoreCase (substring))
return true;
return false;
}
static LicenseState::Type getBestLicenseTypeFromLicenses (const StringArray& licenses)
{
if (stringArrayContainsSubstring (licenses, "juce-pro")) return LicenseState::Type::pro;
else if (stringArrayContainsSubstring (licenses, "juce-indie")) return LicenseState::Type::indie;
else if (stringArrayContainsSubstring (licenses, "juce-personal")) return LicenseState::Type::personal;
else if (stringArrayContainsSubstring (licenses, "juce-edu")) return LicenseState::Type::edu;
return LicenseState::Type::noLicenseChosenYet;
}
void updateIfChanged (const LicenseState& newState)
{
LicenseState updatedState (owner.state);
bool changed = false;
bool shouldUpdateLicenseType = (newState.type != LicenseState::Type::noLicenseChosenYet
|| updatedState.type == LicenseState::Type::notLoggedIn);
if (newState.type != LicenseState::Type::notLoggedIn) updatedState.avatar = newState.avatar;
if (owner.state.type != newState.type && shouldUpdateLicenseType) { updatedState.type = newState.type; changed = true; }
if (owner.state.authToken != newState.authToken) { updatedState.authToken = newState.authToken; changed = true; }
if (owner.state.username != newState.username) { updatedState.username = newState.username; changed = true; }
if (owner.state.email != newState.email) { updatedState.email = newState.email; changed = true; }
if (owner.state.avatar.isValid() != newState.avatar.isValid()) { changed = true; }
if (changed)
executeOnMessageThreadAndBlock ([this, updatedState] { owner.updateState (updatedState); });
}
//==============================================================================
LicenseController& owner;
bool selectNewLicense;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LicenseThread)
};