Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions src/main/java/world/bentobox/bentobox/util/heads/HeadCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,30 @@ public HeadCache(String userName,
// ---------------------------------------------------------------------


/**
* Checks if this cache holds a usable skin texture.
* <p>
* A profile that has a name and a UUID but no texture property is worse than no
* profile at all: the server resolves it against the Mojang session server every time
* the head is shown, which quickly earns an HTTP 429 and still renders a default skin.
*
* @return {@code true} if the cached profile has a skin texture.
* @since 3.22.3
*/
public boolean hasTexture()
{
try
{
return this.playerProfile != null && this.playerProfile.getTextures().getSkin() != null;
}
catch (Exception e)
{
// Treat an unreadable profile as having no texture.
return false;
}
}


/**
* Returns a new Player head with a cached texture. Be AWARE, usage does not use clone
* method. If for some reason item stack is stored directly, then use clone in return
Expand All @@ -94,8 +118,8 @@ public ItemStack getPlayerHead()
ItemStack item = new ItemStack(Material.PLAYER_HEAD);
SkullMeta meta = (SkullMeta) item.getItemMeta();

// Set correct Skull texture
if (meta != null && this.playerProfile != null)
// Set correct Skull texture. Only if the texture is actually known - see hasTexture.
if (meta != null && this.hasTexture())
{
try {
meta.setOwnerProfile(this.playerProfile);
Expand Down
16 changes: 13 additions & 3 deletions src/main/java/world/bentobox/bentobox/util/heads/HeadGetter.java
Original file line number Diff line number Diff line change
Expand Up @@ -176,11 +176,21 @@ private void runPlayerHeadGetter() {
HeadGetter.createProfile(userName, userId, HeadGetter.getTextureFromUUID(userId)));
}

// Save in cache
HeadGetter.cachedHeads.put(userName, cache);
// Save in cache. A failed lookup must not evict a texture we already
// have, otherwise one rate-limited call costs a working head for the
// whole cache period.
HeadCache previous = HeadGetter.cachedHeads.get(userName);

if (cache.hasTexture() || previous == null || !previous.hasTexture()) {
HeadGetter.cachedHeads.put(userName, cache);
} else {
cache = previous;
}

// Tell requesters the head came in, but only if the texture is usable.
if (cache.playerProfile != null && HeadGetter.headRequesters.containsKey(userName)) {
// Handing out a profile with no texture makes the server look it up at
// Mojang every time the head is shown, which ends in HTTP 429s.
if (cache.hasTexture() && HeadGetter.headRequesters.containsKey(userName)) {
for (HeadRequester req : HeadGetter.headRequesters.get(userName)) {
elementEntry.getValue().setHead(cache.getPlayerHead());

Expand Down
Loading