Improved background jobs
This commit is contained in:
parent
bdb8d983d2
commit
e07a865d9c
@ -1,9 +1,14 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "image"
|
import (
|
||||||
|
"image"
|
||||||
|
|
||||||
|
"github.com/animenotifier/arn"
|
||||||
|
)
|
||||||
|
|
||||||
// Avatar represents a single image and the name of the format.
|
// Avatar represents a single image and the name of the format.
|
||||||
type Avatar struct {
|
type Avatar struct {
|
||||||
|
User *arn.User
|
||||||
Image image.Image
|
Image image.Image
|
||||||
Data []byte
|
Data []byte
|
||||||
Format string
|
Format string
|
||||||
|
65
jobs/avatars/AvatarOriginalFileOutput.go
Normal file
65
jobs/avatars/AvatarOriginalFileOutput.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"image/gif"
|
||||||
|
"image/jpeg"
|
||||||
|
"image/png"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/animenotifier/arn"
|
||||||
|
"github.com/nfnt/resize"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AvatarOriginalFileOutput ...
|
||||||
|
type AvatarOriginalFileOutput struct {
|
||||||
|
Directory string
|
||||||
|
Size int
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveAvatar writes the original avatar to the file system.
|
||||||
|
func (output *AvatarOriginalFileOutput) SaveAvatar(avatar *Avatar) error {
|
||||||
|
// Determine file extension
|
||||||
|
extension := ""
|
||||||
|
|
||||||
|
switch avatar.Format {
|
||||||
|
case "jpg", "jpeg":
|
||||||
|
extension = ".jpg"
|
||||||
|
case "png":
|
||||||
|
extension = ".png"
|
||||||
|
case "gif":
|
||||||
|
extension = ".gif"
|
||||||
|
default:
|
||||||
|
return errors.New("Unknown format: " + avatar.Format)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize if needed
|
||||||
|
data := avatar.Data
|
||||||
|
img := avatar.Image
|
||||||
|
|
||||||
|
if img.Bounds().Dx() != output.Size {
|
||||||
|
img = resize.Resize(arn.AvatarSmallSize, 0, img, resize.Lanczos3)
|
||||||
|
buffer := new(bytes.Buffer)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
switch extension {
|
||||||
|
case ".jpg":
|
||||||
|
err = jpeg.Encode(buffer, img, nil)
|
||||||
|
case ".png":
|
||||||
|
err = png.Encode(buffer, img)
|
||||||
|
case ".gif":
|
||||||
|
err = gif.Encode(buffer, img, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
data = buffer.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write to file
|
||||||
|
fileName := output.Directory + avatar.User.ID + extension
|
||||||
|
return ioutil.WriteFile(fileName, data, 0644)
|
||||||
|
}
|
27
jobs/avatars/AvatarWebPFileOutput.go
Normal file
27
jobs/avatars/AvatarWebPFileOutput.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/animenotifier/arn"
|
||||||
|
"github.com/nfnt/resize"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AvatarWebPFileOutput ...
|
||||||
|
type AvatarWebPFileOutput struct {
|
||||||
|
Directory string
|
||||||
|
Size int
|
||||||
|
Quality float32
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveAvatar writes the avatar in WebP format to the file system.
|
||||||
|
func (output *AvatarWebPFileOutput) SaveAvatar(avatar *Avatar) error {
|
||||||
|
img := avatar.Image
|
||||||
|
|
||||||
|
// Resize if needed
|
||||||
|
if img.Bounds().Dx() != output.Size {
|
||||||
|
img = resize.Resize(arn.AvatarSmallSize, 0, img, resize.Lanczos3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write to file
|
||||||
|
fileName := output.Directory + avatar.User.ID + ".webp"
|
||||||
|
return arn.SaveWebP(img, fileName, output.Quality)
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
// AvatarWriter represents a system that saves an avatar locally (in database or as a file, e.g.)
|
// AvatarOutput represents a system that saves an avatar locally (in database or as a file, e.g.)
|
||||||
type AvatarWriter interface {
|
type AvatarOutput interface {
|
||||||
SaveAvatar(*Avatar) error
|
SaveAvatar(*Avatar) error
|
||||||
}
|
}
|
||||||
|
@ -1 +0,0 @@
|
|||||||
package main
|
|
@ -38,6 +38,7 @@ func (source *Gravatar) GetAvatar(user *arn.User) *Avatar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &Avatar{
|
return &Avatar{
|
||||||
|
User: user,
|
||||||
Image: img,
|
Image: img,
|
||||||
Data: data,
|
Data: data,
|
||||||
Format: format,
|
Format: format,
|
||||||
|
@ -1,79 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
|
|
||||||
"github.com/animenotifier/arn"
|
|
||||||
"github.com/fatih/color"
|
|
||||||
"github.com/parnurzeal/gorequest"
|
|
||||||
gravatar "github.com/ungerik/go-gravatar"
|
|
||||||
)
|
|
||||||
|
|
||||||
func findOriginalAvatar(user *arn.User) string {
|
|
||||||
return arn.FindFileWithExtension(
|
|
||||||
user.ID,
|
|
||||||
avatarsDirectoryOriginal,
|
|
||||||
[]string{
|
|
||||||
".jpg",
|
|
||||||
".png",
|
|
||||||
".gif",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func downloadAvatar(user *arn.User) bool {
|
|
||||||
if user.Email == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
directory := avatarsDirectoryOriginal
|
|
||||||
fileName := directory + user.ID
|
|
||||||
|
|
||||||
// Build URL
|
|
||||||
url := gravatar.Url(user.Email) + "?s=" + fmt.Sprint(arn.AvatarMaxSize) + "&d=404&r=pg"
|
|
||||||
|
|
||||||
// Skip existing avatars
|
|
||||||
if findOriginalAvatar(user) != "" {
|
|
||||||
color.Yellow(user.Nick)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Download
|
|
||||||
response, data, err := gorequest.New().Get(url).EndBytes()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
color.Red(user.Nick)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
contentType := response.Header.Get("content-type")
|
|
||||||
|
|
||||||
if response.StatusCode != 200 {
|
|
||||||
color.Red(user.Nick)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine file extension
|
|
||||||
extension := ""
|
|
||||||
|
|
||||||
switch contentType {
|
|
||||||
case "image/jpeg":
|
|
||||||
extension = ".jpg"
|
|
||||||
case "image/png":
|
|
||||||
extension = ".png"
|
|
||||||
case "image/gif":
|
|
||||||
extension = ".gif"
|
|
||||||
case "image/webp":
|
|
||||||
extension = ".webp"
|
|
||||||
}
|
|
||||||
|
|
||||||
fileName += extension
|
|
||||||
|
|
||||||
// Write to disk
|
|
||||||
ioutil.WriteFile(fileName, data, 0644)
|
|
||||||
|
|
||||||
color.Green(user.Nick)
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
@ -3,21 +3,25 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
_ "image/gif"
|
||||||
|
_ "image/jpeg"
|
||||||
|
_ "image/png"
|
||||||
|
|
||||||
"github.com/animenotifier/arn"
|
"github.com/animenotifier/arn"
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
networkRateLimit = 100 * time.Millisecond
|
networkRateLimit = 100 * time.Millisecond
|
||||||
avatarsDirectoryOriginal = "images/avatars/large/original/"
|
webPQuality = 80
|
||||||
avatarsDirectoryWebP = "images/avatars/large/webp/"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var avatarSources []AvatarSource
|
var avatarSources []AvatarSource
|
||||||
var avatarWriters []AvatarWriter
|
var avatarOutputs []AvatarOutput
|
||||||
|
|
||||||
// Main
|
// Main
|
||||||
func main() {
|
func main() {
|
||||||
@ -29,6 +33,28 @@ func main() {
|
|||||||
&Gravatar{},
|
&Gravatar{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Define the avatar outputs
|
||||||
|
avatarOutputs = []AvatarOutput{
|
||||||
|
&AvatarOriginalFileOutput{
|
||||||
|
Directory: "images/avatars/large/original/",
|
||||||
|
Size: arn.AvatarMaxSize,
|
||||||
|
},
|
||||||
|
&AvatarOriginalFileOutput{
|
||||||
|
Directory: "images/avatars/small/original/",
|
||||||
|
Size: arn.AvatarSmallSize,
|
||||||
|
},
|
||||||
|
&AvatarWebPFileOutput{
|
||||||
|
Directory: "images/avatars/large/webp/",
|
||||||
|
Size: arn.AvatarMaxSize,
|
||||||
|
Quality: webPQuality,
|
||||||
|
},
|
||||||
|
&AvatarWebPFileOutput{
|
||||||
|
Directory: "images/avatars/small/webp/",
|
||||||
|
Size: arn.AvatarSmallSize,
|
||||||
|
Quality: webPQuality,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// Stream of all users
|
// Stream of all users
|
||||||
users, _ := arn.AllUsers()
|
users, _ := arn.AllUsers()
|
||||||
|
|
||||||
@ -58,29 +84,29 @@ func StartWorkers(queue chan *arn.User, rateLimit time.Duration, work func(*arn.
|
|||||||
|
|
||||||
// Work handles a single user.
|
// Work handles a single user.
|
||||||
func Work(user *arn.User) {
|
func Work(user *arn.User) {
|
||||||
|
user.Avatar = ""
|
||||||
|
|
||||||
for _, source := range avatarSources {
|
for _, source := range avatarSources {
|
||||||
avatar := source.GetAvatar(user)
|
avatar := source.GetAvatar(user)
|
||||||
|
|
||||||
if avatar == nil {
|
if avatar == nil {
|
||||||
fmt.Println(color.RedString("✘"), user.Nick)
|
fmt.Println(color.RedString("✘"), reflect.TypeOf(source).Elem().Name(), user.Nick)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(color.GreenString("✔"), user.Nick, "|", avatar.Format, avatar.Image.Bounds().Dx(), avatar.Image.Bounds().Dy())
|
for _, writer := range avatarOutputs {
|
||||||
|
err := writer.SaveAvatar(avatar)
|
||||||
|
|
||||||
for _, writer := range avatarWriters {
|
if err != nil {
|
||||||
writer.SaveAvatar(avatar)
|
color.Red(err.Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
fmt.Println(color.GreenString("✔"), user.Nick, "|", avatar.Format, avatar.Image.Bounds().Dx(), avatar.Image.Bounds().Dy())
|
||||||
|
user.Avatar = "/+" + user.Nick + "/avatar"
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// if downloadAvatar(user) {
|
// Save avatar data
|
||||||
// makeWebPAvatar(user)
|
user.Save()
|
||||||
// user.Avatar = "/+" + user.Nick + "/avatar"
|
|
||||||
// } else {
|
|
||||||
// user.Avatar = ""
|
|
||||||
// }
|
|
||||||
|
|
||||||
// user.Save()
|
|
||||||
}
|
}
|
@ -1,51 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "image/gif"
|
|
||||||
_ "image/jpeg"
|
|
||||||
_ "image/png"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/animenotifier/arn"
|
|
||||||
"github.com/fatih/color"
|
|
||||||
"github.com/nfnt/resize"
|
|
||||||
)
|
|
||||||
|
|
||||||
func makeWebPAvatar(user *arn.User) {
|
|
||||||
baseName := findOriginalAvatar(user)
|
|
||||||
|
|
||||||
if baseName == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
original := avatarsDirectoryOriginal + baseName
|
|
||||||
outFile := avatarsDirectoryWebP + user.ID + ".webp"
|
|
||||||
|
|
||||||
err := avatarToWebP(original, outFile, 80)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
color.Red(user.Nick + " [WebP]")
|
|
||||||
} else {
|
|
||||||
color.Green(user.Nick + " [WebP]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func avatarToWebP(in string, out string, quality float32) error {
|
|
||||||
img, _, loadErr := arn.LoadImage(in)
|
|
||||||
|
|
||||||
if loadErr != nil {
|
|
||||||
return loadErr
|
|
||||||
}
|
|
||||||
|
|
||||||
// Big avatar
|
|
||||||
saveErr := arn.SaveWebP(img, out, quality)
|
|
||||||
|
|
||||||
if saveErr != nil {
|
|
||||||
return saveErr
|
|
||||||
}
|
|
||||||
|
|
||||||
// Small avatar
|
|
||||||
smallImg := resize.Resize(arn.AvatarSmallSize, 0, img, resize.Lanczos3)
|
|
||||||
saveErr = arn.SaveWebP(smallImg, strings.Replace(out, "large/", "small/", 1), quality)
|
|
||||||
return saveErr
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user