package com.voxelmodpack.hdskins.skins;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public abstract class AbstractSkinServer implements SkinServer {

    private static final Logger logger = LogManager.getLogger();

    protected static final ExecutorService skinDownloadExecutor = Executors.newCachedThreadPool();
    protected static final ListeningExecutorService skinUploadExecutor = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor());

    public static final MinecraftTexturesPayload EMPTY_PAYLOAD = new MinecraftTexturesPayload();

    private LoadingCache<GameProfile, Optional<MinecraftTexturesPayload>> skins = CacheBuilder.newBuilder()
            .initialCapacity(20)
            .maximumSize(100)
            .expireAfterWrite(4, TimeUnit.HOURS)
            .build(AsyncCacheLoader.create(new CacheLoader<GameProfile, Optional<MinecraftTexturesPayload>>() {

                @Override
                public Optional<MinecraftTexturesPayload> load(GameProfile key) {
                    return loadProfileData(key);
                }
            }, Optional.empty(), skinDownloadExecutor));

    protected abstract Optional<MinecraftTexturesPayload> loadProfileData(GameProfile profile);

    @Override
    public final Optional<MinecraftTexturesPayload> getProfileData(GameProfile profile) {

        boolean was = !skins.asMap().containsKey(profile);
        Optional<MinecraftTexturesPayload> textures = skins.getUnchecked(profile);
        // This is the initial value. Refreshing will load it syncronously.
        if (was) {
            skins.refresh(profile);
        }
        return textures;
    }

    @Override
    public void clearCache() {
        skins.invalidateAll();
    }

}

@@ -34,16 +34,61 @@
 skins;%0A%0A
+import com.google.common.base.Preconditions;%0A
 import c
@@ -120,24 +120,24 @@
 cheBuilder;%0A
-
 import com.g
@@ -1061,106 +1061,8 @@
 );%0A%0A
-    public static final MinecraftTexturesPayload EMPTY_PAYLOAD = new MinecraftTexturesPayload();%0A%0A
     
@@ -1494,24 +1494,249 @@
 file key) %7B%0A
+                    Preconditions.checkNotNull(key, %22profile cannot be null%22);%0A                    // prevent race condition where one server responds faster than the previous one%0A                    synchronized (key) %7B%0A    
             
@@ -1772,16 +1772,38 @@
 a(key);%0A
+                    %7D%0A
         
@@ -1973,32 +1973,32 @@
 %0A%0A    @Override%0A
+
     public final
@@ -2067,25 +2067,24 @@
  profile) %7B%0A
-%0A
         bool

Fix race condition where one server will respond first if it happens to be faster.