More code cleanup

This commit is contained in:
Jacob Gunther
2023-08-09 19:39:46 -05:00
parent e1464a5616
commit 4adcd85f4b
7 changed files with 186 additions and 183 deletions

View File

@@ -1,12 +1,9 @@
package main
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"image"
"log"
)
type ResultCacheKey struct {
@@ -31,9 +28,7 @@ func GetResultCacheKey(uuid, renderType string, opts *QueryParams) (string, erro
return "", err
}
hash := sha256.Sum256(rawKeyData)
return fmt.Sprintf("result:%s", hex.EncodeToString(hash[:])), nil
return fmt.Sprintf("result:%s", SHA256(rawKeyData)), nil
}
// GetCachedRenderResult returns the render result from Redis cache, or nil if it does not exist or cache is disabled.
@@ -81,12 +76,26 @@ func GetCachedSkin(uuid string) (*image.NRGBA, bool, error) {
return nil, false, err
}
if config.Environment == "development" {
log.Printf("Retrieved player skin from cache (uuid=%s, slim=%v)\n", uuid, slim)
}
return cache, slim, nil
}
return nil, false, nil
}
func SetCachedSkin(uuid string, value []byte, isSlim bool) error {
if err := r.Set(fmt.Sprintf("skin:%s", uuid), value, *config.Cache.SkinCacheDuration); err != nil {
return err
}
if isSlim {
if err := r.Set(fmt.Sprintf("slim:%s", uuid), "true", *config.Cache.SkinCacheDuration); err != nil {
return err
}
} else {
if err := r.Delete(fmt.Sprintf("slim:%s", uuid)); err != nil {
return err
}
}
return nil
}

View File

@@ -13,14 +13,8 @@ var (
Environment: "development",
Host: "127.0.0.1",
Port: 3001,
Redis: RedisConfig{
Host: "127.0.0.1",
Port: 6379,
User: "",
Password: "",
Database: 0,
},
Routes: RoutesConfig{
Redis: "redis://127.0.0.1:6379/0",
Routes: Routes{
Face: RouteConfig{
DefaultOverlay: true,
DefaultDownload: false,
@@ -84,16 +78,16 @@ var (
// Config is the root configuration object for the application.
type Config struct {
Environment string `yaml:"environment"`
Host string `yaml:"host"`
Port uint16 `yaml:"port"`
Redis RedisConfig `yaml:"redis"`
Routes RoutesConfig `yaml:"routes"`
Cache CacheConfig `yaml:"cache"`
Environment string `yaml:"environment"`
Host string `yaml:"host"`
Port uint16 `yaml:"port"`
Redis string `yaml:"redis"`
Routes Routes `yaml:"routes"`
Cache CacheConfig `yaml:"cache"`
}
// RoutesConfig is the configuration data of all API routes.
type RoutesConfig struct {
// Routes is the configuration data of all API routes.
type Routes struct {
Face RouteConfig `yaml:"face"`
Head RouteConfig `yaml:"head"`
FullBody RouteConfig `yaml:"full_body"`
@@ -113,15 +107,6 @@ type RouteConfig struct {
MaxScale int `yaml:"max_scale"`
}
// RedisConfig is the configuration data used to connect to Redis.
type RedisConfig struct {
Host string `yaml:"host"`
Port uint16 `yaml:"port"`
User string `yaml:"user"`
Password string `yaml:"password"`
Database int `yaml:"database"`
}
// CacheConfig is the configuration data used to set TTL values for Redis keys.
type CacheConfig struct {
SkinCacheDuration *time.Duration `yaml:"skin_cache_duration"`

View File

@@ -20,8 +20,8 @@ type MinecraftProfile struct {
} `json:"properties"`
}
// MinecraftDecodedTextures is the decoded object of the base64-encoded values property in a MinecraftProfile properties value.
type MinecraftDecodedTextures struct {
// DecodedTextures is the decoded object of the base64-encoded values property in a MinecraftProfile properties value.
type DecodedTextures struct {
Timestamp int64 `json:"timestamp"`
UUID string `json:"uuid"`
Username string `json:"username"`
@@ -47,7 +47,7 @@ func GetMinecraftProfile(uuid string) (*MinecraftProfile, error) {
return nil, err
}
req.Header.Set("User-Agent", "mineatar.io Skin Render API")
req.Header.Set("User-Agent", "mineatar.io")
resp, err := http.DefaultClient.Do(req)
@@ -71,7 +71,7 @@ func GetMinecraftProfile(uuid string) (*MinecraftProfile, error) {
return nil, err
}
response := MinecraftProfile{}
var response MinecraftProfile
if err = json.Unmarshal(body, &response); err != nil {
return nil, err
@@ -80,15 +80,15 @@ func GetMinecraftProfile(uuid string) (*MinecraftProfile, error) {
return &response, nil
}
// GetDecodedTexturesValue decodes the values from a MinecraftProfileTextures texture value.
func GetDecodedTexturesValue(value string) (*MinecraftDecodedTextures, error) {
// DecodeTexturesValue decodes the value from a MinecraftProfile texture property.
func DecodeTexturesValue(value string) (*DecodedTextures, error) {
rawResult, err := base64.StdEncoding.DecodeString(value)
if err != nil {
return nil, err
}
result := MinecraftDecodedTextures{}
var result DecodedTextures
if err = json.Unmarshal(rawResult, &result); err != nil {
return nil, err

View File

@@ -3,7 +3,6 @@ package main
import (
"bytes"
"context"
"fmt"
"image"
"image/draw"
"time"
@@ -24,17 +23,18 @@ type Redis struct {
}
// Connect connects to the Redis server using the configuration values provided.
func (r *Redis) Connect(conf RedisConfig) error {
func (r *Redis) Connect(url string) error {
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
defer cancel()
r.Client = redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("%s:%d", conf.Host, conf.Port),
Username: conf.User,
Password: conf.Password,
DB: conf.Database,
})
opts, err := redis.ParseURL(url)
if err != nil {
return err
}
r.Client = redis.NewClient(opts)
if err := r.Client.Ping(ctx).Err(); err != nil {
return err

123
src/renderer.go Normal file
View File

@@ -0,0 +1,123 @@
package main
import (
"fmt"
"image"
"github.com/mineatar-io/skin-render"
)
var (
RenderTypeFullBody = "fullbody"
RenderTypeFrontBody = "frontbody"
RenderTypeBackBody = "backbody"
RenderTypeLeftBody = "leftbody"
RenderTypeRightBody = "rightbody"
RenderTypeFace = "face"
RenderTypeHead = "head"
)
// Render will render the image using the specified details and return the result.
func Render(renderType, uuid string, rawSkin *image.NRGBA, isSlim bool, opts *QueryParams) ([]byte, bool, error) {
if config.Cache.EnableLocks {
mutex := r.NewMutex(fmt.Sprintf("render-lock:%s-%d-%t-%s", renderType, opts.Scale, opts.Overlay, uuid))
mutex.Lock()
defer mutex.Unlock()
}
// Fetch the existing render from cache if it exists
{
cache, err := GetCachedRenderResult(renderType, uuid, opts)
if err != nil {
return nil, false, err
}
if cache != nil {
return cache, true, nil
}
}
var (
result *image.NRGBA
renderOpts skin.Options = skin.Options{
Overlay: opts.Overlay,
Slim: isSlim,
Scale: opts.Scale,
}
)
// Render the image based on the type provided
{
switch renderType {
case RenderTypeFullBody:
{
result = skin.RenderBody(rawSkin, renderOpts)
break
}
case RenderTypeFrontBody:
{
result = skin.RenderFrontBody(rawSkin, renderOpts)
break
}
case RenderTypeBackBody:
{
result = skin.RenderBackBody(rawSkin, renderOpts)
break
}
case RenderTypeLeftBody:
{
result = skin.RenderLeftBody(rawSkin, renderOpts)
break
}
case RenderTypeRightBody:
{
result = skin.RenderRightBody(rawSkin, renderOpts)
break
}
case RenderTypeHead:
{
result = skin.RenderHead(rawSkin, renderOpts)
break
}
case RenderTypeFace:
{
result = skin.RenderFace(rawSkin, renderOpts)
break
}
default:
panic(fmt.Errorf("unknown render type: %s", renderType))
}
}
var (
data []byte
err error
)
// Encode the image into a PNG in byte-array format
{
data, err = EncodePNG(result)
if err != nil {
return nil, false, err
}
}
// Put the result into the cache for later use
{
if err = SetCachedRenderResult(renderType, uuid, opts, data); err != nil {
return nil, false, err
}
}
return data, false, nil
}

View File

@@ -2,7 +2,9 @@ package main
import (
"bytes"
"crypto/sha256"
_ "embed"
"encoding/hex"
"fmt"
"image"
"image/draw"
@@ -20,14 +22,6 @@ import (
var (
//go:embed favicon.ico
favicon []byte
RenderTypeFullBody = "fullbody"
RenderTypeFrontBody = "frontbody"
RenderTypeBackBody = "backbody"
RenderTypeLeftBody = "leftbody"
RenderTypeRightBody = "rightbody"
RenderTypeFace = "face"
RenderTypeHead = "head"
)
// QueryParams is used by most all API routes as options for how the image should be rendered, or how errors should be handled.
@@ -56,102 +50,6 @@ func Clamp[T int | uint | int8 | uint8 | int16 | uint16 | int32 | uint32 | int64
return value
}
// Render will render the image using the specified details and return the result.
func Render(renderType, uuid string, rawSkin *image.NRGBA, isSlim bool, opts *QueryParams) ([]byte, bool, error) {
if config.Cache.EnableLocks {
mutex := r.NewMutex(fmt.Sprintf("render-lock:%s-%d-%t-%s", renderType, opts.Scale, opts.Overlay, uuid))
mutex.Lock()
defer mutex.Unlock()
}
cache, err := GetCachedRenderResult(renderType, uuid, opts)
if err != nil {
return nil, false, err
}
if cache != nil {
if config.Environment == "development" {
log.Printf("Retrieved render from cache (type=%s, uuid=%s, slim=%v, scale=%d)\n", renderType, uuid, isSlim, opts.Scale)
}
return cache, true, nil
}
var (
result *image.NRGBA
renderOpts skin.Options = skin.Options{
Overlay: opts.Overlay,
Slim: isSlim,
Scale: opts.Scale,
}
)
switch renderType {
case RenderTypeFullBody:
{
result = skin.RenderBody(rawSkin, renderOpts)
break
}
case RenderTypeFrontBody:
{
result = skin.RenderFrontBody(rawSkin, renderOpts)
break
}
case RenderTypeBackBody:
{
result = skin.RenderBackBody(rawSkin, renderOpts)
break
}
case RenderTypeLeftBody:
{
result = skin.RenderLeftBody(rawSkin, renderOpts)
break
}
case RenderTypeRightBody:
{
result = skin.RenderRightBody(rawSkin, renderOpts)
break
}
case RenderTypeHead:
{
result = skin.RenderHead(rawSkin, renderOpts)
break
}
case RenderTypeFace:
{
result = skin.RenderFace(rawSkin, renderOpts)
break
}
default:
panic(fmt.Errorf("unknown render type: %s", renderType))
}
data, err := EncodePNG(result)
if err != nil {
return nil, false, err
}
if err = SetCachedRenderResult(renderType, uuid, opts, data); err != nil {
return nil, false, err
}
if config.Environment == "development" {
log.Printf("Rendered image (type=%s, uuid=%s, slim=%v, scale=%d)\n", renderType, uuid, isSlim, opts.Scale)
}
return data, false, nil
}
// ExtractUUID returns the UUID from the route param, allowing values such as "<uuid>.png" to be returned as "<uuid>".
func ExtractUUID(ctx *fiber.Ctx) string {
return strings.Split(ctx.Params("uuid"), ".")[0]
@@ -218,13 +116,13 @@ func GetPlayerSkin(uuid string) (*image.NRGBA, bool, error) {
}
var (
err error = nil
skinImage *image.NRGBA = nil
rawSkin []byte = nil
isSlim bool = skin.IsSlimFromUUID(uuid)
profile *MinecraftProfile = nil
rawTextures string = ""
texturesProperty *MinecraftDecodedTextures = nil
err error = nil
skinImage *image.NRGBA = nil
rawSkin []byte = nil
isSlim bool = skin.IsSlimFromUUID(uuid)
profile *MinecraftProfile = nil
rawTextures string = ""
texturesProperty *DecodedTextures = nil
)
// Get the textures metadata from Mojang about the Minecraft player
@@ -259,7 +157,7 @@ func GetPlayerSkin(uuid string) (*image.NRGBA, bool, error) {
// Decode the raw textures value returned from the player's properties
{
if texturesProperty, err = GetDecodedTexturesValue(rawTextures); err != nil {
if texturesProperty, err = DecodeTexturesValue(rawTextures); err != nil {
return nil, false, err
}
@@ -283,23 +181,9 @@ func GetPlayerSkin(uuid string) (*image.NRGBA, bool, error) {
// Put the skin into cache so it can be used for future requests
if config.Cache.SkinCacheDuration != nil {
if err = r.Set(fmt.Sprintf("skin:%s", uuid), rawSkin, *config.Cache.SkinCacheDuration); err != nil {
if err = SetCachedSkin(uuid, rawSkin, isSlim); err != nil {
return nil, false, err
}
if isSlim {
if err = r.Set(fmt.Sprintf("slim:%s", uuid), "true", *config.Cache.SkinCacheDuration); err != nil {
return nil, false, err
}
} else {
if err = r.Delete(fmt.Sprintf("slim:%s", uuid)); err != nil {
return nil, false, err
}
}
}
if config.Environment == "development" {
log.Printf("Fetched player skin from Mojang (uuid=%s, slim=%v)\n", uuid, isSlim)
}
return skinImage, isSlim, nil
@@ -357,3 +241,10 @@ func GetInstanceID() (uint16, error) {
return 0, nil
}
// SHA256 computes the SHA-256 hash of the input byte-array.
func SHA256(value []byte) string {
hash := sha256.Sum256(value)
return hex.EncodeToString(hash[:])
}