add image resize, default avatars, and other fixes
This commit is contained in:
		
							parent
							
								
									7878834155
								
							
						
					
					
						commit
						b3922980db
					
				
							
								
								
									
										2
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.mod
									
									
									
									
									
								
							@ -3,12 +3,14 @@ module github.com/sour-is/keyproofs
 | 
				
			|||||||
go 1.15
 | 
					go 1.15
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require (
 | 
					require (
 | 
				
			||||||
 | 
						github.com/disintegration/imaging v1.6.2
 | 
				
			||||||
	github.com/fsnotify/fsnotify v1.4.7
 | 
						github.com/fsnotify/fsnotify v1.4.7
 | 
				
			||||||
	github.com/go-chi/chi v4.1.2+incompatible
 | 
						github.com/go-chi/chi v4.1.2+incompatible
 | 
				
			||||||
	github.com/google/go-cmp v0.5.4 // indirect
 | 
						github.com/google/go-cmp v0.5.4 // indirect
 | 
				
			||||||
	github.com/hashicorp/golang-lru v0.5.4
 | 
						github.com/hashicorp/golang-lru v0.5.4
 | 
				
			||||||
	github.com/joho/godotenv v1.3.0
 | 
						github.com/joho/godotenv v1.3.0
 | 
				
			||||||
	github.com/lucasb-eyer/go-colorful v1.0.3
 | 
						github.com/lucasb-eyer/go-colorful v1.0.3
 | 
				
			||||||
 | 
						github.com/nullrocks/identicon v0.0.0-20180626043057-7875f45b0022
 | 
				
			||||||
	github.com/rs/cors v1.7.0
 | 
						github.com/rs/cors v1.7.0
 | 
				
			||||||
	github.com/rs/zerolog v1.20.0
 | 
						github.com/rs/zerolog v1.20.0
 | 
				
			||||||
	github.com/russross/blackfriday v1.5.2
 | 
						github.com/russross/blackfriday v1.5.2
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										6
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								go.sum
									
									
									
									
									
								
							@ -9,6 +9,8 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7
 | 
				
			|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
					github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
				
			||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 | 
					github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 | 
				
			||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
					github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
				
			||||||
 | 
					github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
 | 
				
			||||||
 | 
					github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
 | 
				
			||||||
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
 | 
					github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
 | 
				
			||||||
github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 | 
					github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 | 
				
			||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 | 
					github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 | 
				
			||||||
@ -60,6 +62,8 @@ github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc
 | 
				
			|||||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 | 
					github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 | 
				
			||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 | 
					github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 | 
				
			||||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
 | 
					github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
 | 
				
			||||||
 | 
					github.com/nullrocks/identicon v0.0.0-20180626043057-7875f45b0022 h1:Ys0rDzh8s4UMlGaDa1UTA0sfKgvF0hQZzTYX8ktjiDc=
 | 
				
			||||||
 | 
					github.com/nullrocks/identicon v0.0.0-20180626043057-7875f45b0022/go.mod h1:x4NsS+uc7ecH/Cbm9xKQ6XzmJM57rWTkjywjfB2yQ18=
 | 
				
			||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 | 
					github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 | 
				
			||||||
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 | 
					github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 | 
				
			||||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 | 
					github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 | 
				
			||||||
@ -112,6 +116,8 @@ golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 h1:phUcVbl53swtrUN8kQEXFh
 | 
				
			|||||||
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
 | 
					golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
 | 
				
			||||||
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392 h1:xYJJ3S178yv++9zXV/hnr29plCAGO9vAFG9dorqaFQc=
 | 
					golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392 h1:xYJJ3S178yv++9zXV/hnr29plCAGO9vAFG9dorqaFQc=
 | 
				
			||||||
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
 | 
					golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
 | 
				
			||||||
 | 
					golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
 | 
				
			||||||
 | 
					golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 | 
				
			||||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 | 
					golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 | 
				
			||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
					golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
				
			||||||
golang.org/x/net v0.0.0-20181102091132-c10e9556a7bc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
					golang.org/x/net v0.0.0-20181102091132-c10e9556a7bc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										6
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								main.go
									
									
									
									
									
								
							@ -89,6 +89,12 @@ func run(ctx context.Context) error {
 | 
				
			|||||||
	mux := chi.NewRouter()
 | 
						mux := chi.NewRouter()
 | 
				
			||||||
	mux.Use(
 | 
						mux.Use(
 | 
				
			||||||
		cfg.ApplyHTTP,
 | 
							cfg.ApplyHTTP,
 | 
				
			||||||
 | 
							func(next http.Handler) http.Handler {
 | 
				
			||||||
 | 
								return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
									r = r.WithContext(log.WithContext(r.Context()))
 | 
				
			||||||
 | 
									next.ServeHTTP(w, r)
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		secHeaders,
 | 
							secHeaders,
 | 
				
			||||||
		cors.New(cors.Options{
 | 
							cors.New(cors.Options{
 | 
				
			||||||
			AllowCredentials: true,
 | 
								AllowCredentials: true,
 | 
				
			||||||
 | 
				
			|||||||
@ -22,12 +22,17 @@ func getOpenPGPkey(ctx context.Context, id string) (entity *Entity, err error) {
 | 
				
			|||||||
		addr := "https://keys.openpgp.org/vks/v1/by-fingerprint/" + strings.ToUpper(id)
 | 
							addr := "https://keys.openpgp.org/vks/v1/by-fingerprint/" + strings.ToUpper(id)
 | 
				
			||||||
		return getEntityHTTP(ctx, addr, true)
 | 
							return getEntityHTTP(ctx, addr, true)
 | 
				
			||||||
	} else if email, err := mail.ParseAddress(id); err == nil {
 | 
						} else if email, err := mail.ParseAddress(id); err == nil {
 | 
				
			||||||
		addr := getWKDPubKeyAddr(email)
 | 
							addr, advAddr := getWKDPubKeyAddr(email)
 | 
				
			||||||
		req, err := getEntityHTTP(ctx, addr, false)
 | 
							req, err := getEntityHTTP(ctx, addr, false)
 | 
				
			||||||
		if err == nil {
 | 
							if err == nil {
 | 
				
			||||||
			return req, err
 | 
								return req, err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							req, err = getEntityHTTP(ctx, advAddr, false)
 | 
				
			||||||
 | 
							if err == nil {
 | 
				
			||||||
 | 
								return req, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		addr = "https://keys.openpgp.org/vks/v1/by-email/" + url.QueryEscape(id)
 | 
							addr = "https://keys.openpgp.org/vks/v1/by-email/" + url.QueryEscape(id)
 | 
				
			||||||
		return getEntityHTTP(ctx, addr, true)
 | 
							return getEntityHTTP(ctx, addr, true)
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
@ -44,16 +49,15 @@ func getEntityHTTP(ctx context.Context, url string, useArmored bool) (entity *En
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	cl := http.Client{}
 | 
						cl := http.Client{}
 | 
				
			||||||
	resp, err := cl.Do(req)
 | 
						resp, err := cl.Do(req)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return entity, fmt.Errorf("Requesting key: %w\nRemote URL: %v", err, url)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	log.Debug().
 | 
						log.Debug().
 | 
				
			||||||
		Bool("useArmored", useArmored).
 | 
							Bool("useArmored", useArmored).
 | 
				
			||||||
		Str("status", resp.Status).
 | 
							Str("status", resp.Status).
 | 
				
			||||||
		Str("url", url).
 | 
							Str("url", url).
 | 
				
			||||||
		Msg("getEntityHTTP")
 | 
							Msg("getEntityHTTP")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return entity, fmt.Errorf("Requesting key: %w\nRemote URL: %v", err, url)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if resp.StatusCode != 200 {
 | 
						if resp.StatusCode != 200 {
 | 
				
			||||||
		return entity, fmt.Errorf("bad response from remote: %s\nRemote URL: %v", resp.Status, url)
 | 
							return entity, fmt.Errorf("bad response from remote: %s\nRemote URL: %v", resp.Status, url)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -194,11 +198,11 @@ func isFingerprint(s string) bool {
 | 
				
			|||||||
	return true
 | 
						return true
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func getWKDPubKeyAddr(email *mail.Address) string {
 | 
					func getWKDPubKeyAddr(email *mail.Address) (string, string) {
 | 
				
			||||||
	parts := strings.SplitN(email.Address, "@", 2)
 | 
						parts := strings.SplitN(email.Address, "@", 2)
 | 
				
			||||||
 | 
					 | 
				
			||||||
	hash := sha1.Sum([]byte(parts[0]))
 | 
						hash := sha1.Sum([]byte(parts[0]))
 | 
				
			||||||
	lp := zbase32.EncodeToString(hash[:])
 | 
						lp := zbase32.EncodeToString(hash[:])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return fmt.Sprintf("https://%s/.well-known/openpgpkey/hu/%s", parts[1], lp)
 | 
						return fmt.Sprintf("https://%s/.well-known/openpgpkey/hu/%s", parts[1], lp),
 | 
				
			||||||
 | 
							fmt.Sprintf("https://openpgpkey.%s/.well-known/openpgpkey/hu/%s/%s", parts[1], parts[1], lp)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -3,17 +3,23 @@ package keyproofs
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"crypto/md5"
 | 
						"crypto/md5"
 | 
				
			||||||
	"crypto/sha1"
 | 
						"crypto/sha256"
 | 
				
			||||||
 | 
						"encoding/base64"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
						"hash"
 | 
				
			||||||
	"io"
 | 
						"io"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"path/filepath"
 | 
						"path/filepath"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/disintegration/imaging"
 | 
				
			||||||
	"github.com/fsnotify/fsnotify"
 | 
						"github.com/fsnotify/fsnotify"
 | 
				
			||||||
	"github.com/go-chi/chi"
 | 
						"github.com/go-chi/chi"
 | 
				
			||||||
 | 
						"github.com/nullrocks/identicon"
 | 
				
			||||||
	"github.com/rs/zerolog/log"
 | 
						"github.com/rs/zerolog/log"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/sour-is/keyproofs/pkg/graceful"
 | 
						"github.com/sour-is/keyproofs/pkg/graceful"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -57,14 +63,14 @@ func NewAvatarApp(ctx context.Context, path string) (*avatarApp, error) {
 | 
				
			|||||||
					path = filepath.Dir(op.Name)
 | 
										path = filepath.Dir(op.Name)
 | 
				
			||||||
					kind := filepath.Base(path)
 | 
										kind := filepath.Base(path)
 | 
				
			||||||
					name := filepath.Base(op.Name)
 | 
										name := filepath.Base(op.Name)
 | 
				
			||||||
					if err := createLinks(app.path, kind, name); err != nil {
 | 
										if err := app.createLinks(kind, name); err != nil {
 | 
				
			||||||
						fmt.Println(err)
 | 
											fmt.Println(err)
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
				case fsnotify.Remove, fsnotify.Rename:
 | 
									case fsnotify.Remove, fsnotify.Rename:
 | 
				
			||||||
					path = filepath.Dir(op.Name)
 | 
										path = filepath.Dir(op.Name)
 | 
				
			||||||
					kind := filepath.Base(path)
 | 
										kind := filepath.Base(path)
 | 
				
			||||||
					name := filepath.Base(op.Name)
 | 
										name := filepath.Base(op.Name)
 | 
				
			||||||
					if err := removeLinks(app.path, kind, name); err != nil {
 | 
										if err := app.removeLinks(kind, name); err != nil {
 | 
				
			||||||
						log.Error().Err(err).Send()
 | 
											log.Error().Err(err).Send()
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
				default:
 | 
									default:
 | 
				
			||||||
@ -106,7 +112,7 @@ func (app *avatarApp) CheckFiles(ctx context.Context) error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		log.Debug().Msgf("link: %s %s %s", app.path, kind, name)
 | 
							log.Debug().Msgf("link: %s %s %s", app.path, kind, name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return createLinks(app.path, kind, name)
 | 
							return app.createLinks(kind, name)
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -118,13 +124,19 @@ func (app *avatarApp) get(w http.ResponseWriter, r *http.Request) {
 | 
				
			|||||||
	kind := chi.URLParam(r, "kind")
 | 
						kind := chi.URLParam(r, "kind")
 | 
				
			||||||
	hash := chi.URLParam(r, "hash")
 | 
						hash := chi.URLParam(r, "hash")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sizeW, sizeH, resize := 0, 0, false
 | 
				
			||||||
 | 
						if s, err := strconv.Atoi(r.URL.Query().Get("s")); err == nil && s > 0 {
 | 
				
			||||||
 | 
							sizeW, sizeH, resize = sizeByKind(kind, s)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						log.Debug().Int("width", sizeW).Int("height", sizeH).Bool("resize", resize).Str("kind", kind).Msg("Get Image")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if strings.ContainsRune(hash, '@') {
 | 
						if strings.ContainsRune(hash, '@') {
 | 
				
			||||||
		avatarHost, _, err := styleSRV(r.Context(), hash)
 | 
							avatarHost, _, err := styleSRV(r.Context(), hash)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			writeText(w, 500, err.Error())
 | 
								writeText(w, 500, err.Error())
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		hash = hashSHA1(strings.ToLower(hash))
 | 
							hash = hashSHA256(strings.ToLower(hash))
 | 
				
			||||||
		http.Redirect(w, r, fmt.Sprintf("https://%s/%s/%s?%s", avatarHost, kind, hash, r.URL.RawQuery), 301)
 | 
							http.Redirect(w, r, fmt.Sprintf("https://%s/%s/%s?%s", avatarHost, kind, hash, r.URL.RawQuery), 301)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -132,38 +144,89 @@ func (app *avatarApp) get(w http.ResponseWriter, r *http.Request) {
 | 
				
			|||||||
	fname := filepath.Join(app.path, ".links", strings.Join([]string{kind, hash}, "-"))
 | 
						fname := filepath.Join(app.path, ".links", strings.Join([]string{kind, hash}, "-"))
 | 
				
			||||||
	log.Debug().Msgf("path: %s", fname)
 | 
						log.Debug().Msgf("path: %s", fname)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	f, err := os.Open(fname)
 | 
						if !fileExists(fname) {
 | 
				
			||||||
 | 
							switch kind {
 | 
				
			||||||
 | 
							case "avatar":
 | 
				
			||||||
 | 
								ig, err := identicon.New("sour.is", 5, 3)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									writeText(w, 500, err.Error())
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								ii, err := ig.Draw(hash)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									writeText(w, 500, err.Error())
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								w.Header().Set("Content-Type", "image/png")
 | 
				
			||||||
 | 
								w.WriteHeader(200)
 | 
				
			||||||
 | 
								err = ii.Png(clamp(128, 512, sizeW), w)
 | 
				
			||||||
 | 
								log.Error().Err(err).Send()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								sp := strings.SplitN(pixl, ",", 2)
 | 
				
			||||||
 | 
								b, _ := base64.RawStdEncoding.DecodeString(sp[1])
 | 
				
			||||||
 | 
								w.Header().Set("Content-Type", "image/png")
 | 
				
			||||||
 | 
								w.WriteHeader(200)
 | 
				
			||||||
 | 
								if _, err := w.Write(b); err != nil {
 | 
				
			||||||
 | 
									log.Error().Err(err).Send()
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !resize {
 | 
				
			||||||
 | 
							f, err := os.Open(fname)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								writeText(w, 500, err.Error())
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							w.Header().Set("Content-Type", "image/png")
 | 
				
			||||||
 | 
							w.WriteHeader(200)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							_, err = io.Copy(w, f)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Error().Err(err).Send()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						img, err := imaging.Open(fname, imaging.AutoOrientation(true))
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		writeText(w, 500, err.Error())
 | 
							writeText(w, 500, err.Error())
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, err = io.Copy(w, f)
 | 
						img = imaging.Fill(img, sizeW, sizeH, imaging.Center, imaging.Lanczos)
 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		writeText(w, 500, err.Error())
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						w.Header().Set("Content-Type", "image/png")
 | 
				
			||||||
 | 
						w.WriteHeader(200)
 | 
				
			||||||
 | 
						log.Debug().Msg("writing image")
 | 
				
			||||||
 | 
						err = imaging.Encode(w, img, imaging.PNG)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Error().Err(err).Send()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (app *avatarApp) Routes(r *chi.Mux) {
 | 
					func (app *avatarApp) Routes(r *chi.Mux) {
 | 
				
			||||||
	r.MethodFunc("GET", "/{kind:avatar|bg|cover}/{hash}", app.get)
 | 
						r.MethodFunc("GET", "/{kind:avatar|bg|cover}/{hash}", app.get)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func hashString(value string, h hash.Hash) string {
 | 
				
			||||||
 | 
						_, _ = h.Write([]byte(value))
 | 
				
			||||||
 | 
						return fmt.Sprintf("%x", h.Sum(nil))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
func hashMD5(name string) string {
 | 
					func hashMD5(name string) string {
 | 
				
			||||||
	h := md5.New()
 | 
						return hashString(name, md5.New())
 | 
				
			||||||
	_, _ = h.Write([]byte(name))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return fmt.Sprintf("%x", h.Sum(nil))
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
func hashSHA1(name string) string {
 | 
					func hashSHA256(name string) string {
 | 
				
			||||||
	h := sha1.New()
 | 
						return hashString(name, sha256.New())
 | 
				
			||||||
	_, _ = h.Write([]byte(name))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return fmt.Sprintf("%x", h.Sum(nil))
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func createLinks(path, kind, name string) error {
 | 
					func (app *avatarApp) createLinks(kind, name string) error {
 | 
				
			||||||
	if !strings.ContainsRune(name, '@') {
 | 
						if !strings.ContainsRune(name, '@') {
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -172,40 +235,40 @@ func createLinks(path, kind, name string) error {
 | 
				
			|||||||
	name = strings.ToLower(name)
 | 
						name = strings.ToLower(name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	hash := hashMD5(name)
 | 
						hash := hashMD5(name)
 | 
				
			||||||
	link := filepath.Join(path, ".links", strings.Join([]string{kind, hash}, "-"))
 | 
						link := filepath.Join(app.path, ".links", strings.Join([]string{kind, hash}, "-"))
 | 
				
			||||||
	err := replaceLink(src, link)
 | 
						err := app.replaceLink(src, link)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	hash = hashSHA1(name)
 | 
						hash = hashSHA256(name)
 | 
				
			||||||
	link = filepath.Join(path, ".links", strings.Join([]string{kind, hash}, "-"))
 | 
						link = filepath.Join(app.path, ".links", strings.Join([]string{kind, hash}, "-"))
 | 
				
			||||||
	err = replaceLink(src, link)
 | 
						err = app.replaceLink(src, link)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return err
 | 
						return err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func removeLinks(path, kind, name string) error {
 | 
					func (app *avatarApp) removeLinks(kind, name string) error {
 | 
				
			||||||
	if !strings.ContainsRune(name, '@') {
 | 
						if !strings.ContainsRune(name, '@') {
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	name = strings.ToLower(name)
 | 
						name = strings.ToLower(name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	hash := hashMD5(name)
 | 
						hash := hashMD5(name)
 | 
				
			||||||
	link := filepath.Join(path, ".links", strings.Join([]string{kind, hash}, "-"))
 | 
						link := filepath.Join(app.path, ".links", strings.Join([]string{kind, hash}, "-"))
 | 
				
			||||||
	err := os.Remove(link)
 | 
						err := os.Remove(link)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	hash = hashSHA1(name)
 | 
						hash = hashSHA256(name)
 | 
				
			||||||
	link = filepath.Join(path, ".links", strings.Join([]string{kind, hash}, "-"))
 | 
						link = filepath.Join(app.path, ".links", strings.Join([]string{kind, hash}, "-"))
 | 
				
			||||||
	err = os.Remove(link)
 | 
						err = os.Remove(link)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return err
 | 
						return err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func replaceLink(src, link string) error {
 | 
					func (app *avatarApp) replaceLink(src, link string) error {
 | 
				
			||||||
	if dst, err := os.Readlink(link); err != nil {
 | 
						if dst, err := os.Readlink(link); err != nil {
 | 
				
			||||||
		if os.IsNotExist(err) {
 | 
							if os.IsNotExist(err) {
 | 
				
			||||||
			err = os.Symlink(src, link)
 | 
								err = os.Symlink(src, link)
 | 
				
			||||||
@ -228,3 +291,52 @@ func replaceLink(src, link string) error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func fileExists(filename string) bool {
 | 
				
			||||||
 | 
						info, err := os.Stat(filename)
 | 
				
			||||||
 | 
						if os.IsNotExist(err) {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return !info.IsDir()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func sizeByKind(kind string, size int) (sizeW int, sizeH int, resize bool) {
 | 
				
			||||||
 | 
						switch kind {
 | 
				
			||||||
 | 
						case "avatar":
 | 
				
			||||||
 | 
							if size == 0 {
 | 
				
			||||||
 | 
								size = 128
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							sizeW = clamp(128, 640, size)
 | 
				
			||||||
 | 
							sizeH = sizeW
 | 
				
			||||||
 | 
							resize = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						case "cover":
 | 
				
			||||||
 | 
							if size == 0 {
 | 
				
			||||||
 | 
								size = 940
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							sizeW = clamp(640, 1300, size)
 | 
				
			||||||
 | 
							sizeH = ratio(sizeW, 2.7)
 | 
				
			||||||
 | 
							resize = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							return 0, 0, false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ratio(size int, ratio float64) int {
 | 
				
			||||||
 | 
						return int(float64(size) / ratio)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					func clamp(min, max, size int) int {
 | 
				
			||||||
 | 
						if size > max {
 | 
				
			||||||
 | 
							return max
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if size < min {
 | 
				
			||||||
 | 
							return min
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return size
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										212
									
								
								pkg/keyproofs/routes-wkd.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										212
									
								
								pkg/keyproofs/routes-wkd.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,212 @@
 | 
				
			|||||||
 | 
					package keyproofs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/fsnotify/fsnotify"
 | 
				
			||||||
 | 
						"github.com/go-chi/chi"
 | 
				
			||||||
 | 
						"github.com/rs/zerolog/log"
 | 
				
			||||||
 | 
						"github.com/sour-is/keyproofs/pkg/graceful"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type wkdApp struct {
 | 
				
			||||||
 | 
						path   string
 | 
				
			||||||
 | 
						domain string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewWKDApp(ctx context.Context, path, domain string) (*wkdApp, error) {
 | 
				
			||||||
 | 
						log := log.Ctx(ctx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						path = filepath.Clean(path)
 | 
				
			||||||
 | 
						app := &wkdApp{path: path}
 | 
				
			||||||
 | 
						err := app.CheckFiles(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						watch, err := fsnotify.NewWatcher()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, typ := range []string{"keys"} {
 | 
				
			||||||
 | 
							err = watch.Add(filepath.Join(path, typ))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Debug().Msg("startup wkd watcher")
 | 
				
			||||||
 | 
						wg := graceful.WaitGroup(ctx)
 | 
				
			||||||
 | 
						wg.Go(func() error {
 | 
				
			||||||
 | 
							for {
 | 
				
			||||||
 | 
								select {
 | 
				
			||||||
 | 
								case <-ctx.Done():
 | 
				
			||||||
 | 
									log.Debug().Msg("shutdown wkd watcher")
 | 
				
			||||||
 | 
									return nil
 | 
				
			||||||
 | 
								case op := <-watch.Events:
 | 
				
			||||||
 | 
									log.Print(op)
 | 
				
			||||||
 | 
									switch op.Op {
 | 
				
			||||||
 | 
									case fsnotify.Create:
 | 
				
			||||||
 | 
										path = filepath.Dir(op.Name)
 | 
				
			||||||
 | 
										kind := filepath.Base(path)
 | 
				
			||||||
 | 
										name := filepath.Base(op.Name)
 | 
				
			||||||
 | 
										if err := app.createLinks(kind, name); err != nil {
 | 
				
			||||||
 | 
											fmt.Println(err)
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									case fsnotify.Remove, fsnotify.Rename:
 | 
				
			||||||
 | 
										path = filepath.Dir(op.Name)
 | 
				
			||||||
 | 
										kind := filepath.Base(path)
 | 
				
			||||||
 | 
										name := filepath.Base(op.Name)
 | 
				
			||||||
 | 
										if err := app.removeLinks(kind, name); err != nil {
 | 
				
			||||||
 | 
											log.Error().Err(err).Send()
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									default:
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								case err := <-watch.Errors:
 | 
				
			||||||
 | 
									fmt.Println(err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return app, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (app *wkdApp) CheckFiles(ctx context.Context) error {
 | 
				
			||||||
 | 
						log := log.Ctx(ctx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, name := range []string{".links", "wkd"} {
 | 
				
			||||||
 | 
							log.Debug().Msgf("mkdir: %s", filepath.Join(app.path, name))
 | 
				
			||||||
 | 
							err := os.MkdirAll(filepath.Join(app.path, name), 0700)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return filepath.Walk(app.path, func(path string, info os.FileInfo, err error) error {
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if info.IsDir() {
 | 
				
			||||||
 | 
								if info.Name() == ".links" {
 | 
				
			||||||
 | 
									return filepath.SkipDir
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							path = filepath.Dir(path)
 | 
				
			||||||
 | 
							kind := filepath.Base(path)
 | 
				
			||||||
 | 
							name := info.Name()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							log.Debug().Msgf("link: %s %s %s", app.path, kind, name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return app.createLinks(kind, name)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (app *wkdApp) get(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						log := log.Ctx(r.Context())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Print(r.Host)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						kind := chi.URLParam(r, "kind")
 | 
				
			||||||
 | 
						hash := chi.URLParam(r, "hash")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if strings.ContainsRune(hash, '@') {
 | 
				
			||||||
 | 
							avatarHost, _, err := styleSRV(r.Context(), hash)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								writeText(w, 500, err.Error())
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							hash = hashSHA256(strings.ToLower(hash))
 | 
				
			||||||
 | 
							http.Redirect(w, r, fmt.Sprintf("https://%s/%s/%s?%s", avatarHost, kind, hash, r.URL.RawQuery), 301)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fname := filepath.Join(app.path, ".links", strings.Join([]string{kind, hash}, "-"))
 | 
				
			||||||
 | 
						log.Debug().Msgf("path: %s", fname)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						f, err := os.Open(fname)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							writeText(w, 500, err.Error())
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, err = io.Copy(w, f)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							writeText(w, 500, err.Error())
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (app *wkdApp) Routes(r *chi.Mux) {
 | 
				
			||||||
 | 
						r.MethodFunc("GET", "/.well-known/openpgpkey/hu/{hash}", app.get)
 | 
				
			||||||
 | 
						r.MethodFunc("GET", "/.well-known/openpgpkey/hu/{domain}/{hash}", app.get)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (app *wkdApp) createLinks(kind, name string) error {
 | 
				
			||||||
 | 
						if !strings.ContainsRune(name, '@') {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						src := filepath.Join("..", kind, name)
 | 
				
			||||||
 | 
						name = strings.ToLower(name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						hash := hashMD5(name)
 | 
				
			||||||
 | 
						link := filepath.Join(app.path, ".links", strings.Join([]string{kind, hash}, "-"))
 | 
				
			||||||
 | 
						err := app.replaceLink(src, link)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (app *wkdApp) removeLinks(kind, name string) error {
 | 
				
			||||||
 | 
						if !strings.ContainsRune(name, '@') {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						name = strings.ToLower(name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						hash := hashMD5(name)
 | 
				
			||||||
 | 
						link := filepath.Join(app.path, ".links", strings.Join([]string{kind, hash}, "-"))
 | 
				
			||||||
 | 
						err := os.Remove(link)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						hash = hashSHA256(name)
 | 
				
			||||||
 | 
						link = filepath.Join(app.path, ".links", strings.Join([]string{kind, hash}, "-"))
 | 
				
			||||||
 | 
						err = os.Remove(link)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (app *wkdApp) replaceLink(src, link string) error {
 | 
				
			||||||
 | 
						if dst, err := os.Readlink(link); err != nil {
 | 
				
			||||||
 | 
							if os.IsNotExist(err) {
 | 
				
			||||||
 | 
								err = os.Symlink(src, link)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							if dst != src {
 | 
				
			||||||
 | 
								err = os.Remove(link)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								err = os.Symlink(src, link)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user