/
exas.go
128 lines (108 loc) · 3.24 KB
/
exas.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
package exas
import (
"bytes"
"context"
"encoding/json"
"flag"
"fmt"
"io"
"net/http"
"os/exec"
"strings"
"sync"
absto "github.com/ViBiOh/absto/pkg/model"
"github.com/ViBiOh/exas/pkg/geocode"
"github.com/ViBiOh/exas/pkg/model"
"github.com/ViBiOh/flags"
"github.com/ViBiOh/httputils/v4/pkg/amqp"
prom "github.com/ViBiOh/httputils/v4/pkg/prometheus"
"github.com/ViBiOh/httputils/v4/pkg/tracer"
"github.com/prometheus/client_golang/prometheus"
"go.opentelemetry.io/otel/trace"
)
var bufferPool = sync.Pool{
New: func() any {
return bytes.NewBuffer(make([]byte, 32*1024))
},
}
// App of package
type App struct {
storageApp absto.Storage
tracer trace.Tracer
amqpClient *amqp.Client
metric *prometheus.CounterVec
amqpExchange string
amqpRoutingKey string
geocodeApp geocode.App
}
// Config of package
type Config struct {
amqpExchange *string
amqpRoutingKey *string
}
// Flags adds flags for configuring package
func Flags(fs *flag.FlagSet, prefix string, overrides ...flags.Override) Config {
return Config{
amqpExchange: flags.New("Exchange", "AMQP Exchange Name").Prefix(prefix).DocPrefix("exas").String(fs, "fibr", overrides),
amqpRoutingKey: flags.New("RoutingKey", "AMQP Routing Key to fibr").Prefix(prefix).DocPrefix("exas").String(fs, "exif_output", overrides),
}
}
// New creates new App from Config
func New(config Config, geocodeApp geocode.App, prometheusRegisterer prometheus.Registerer, amqpClient *amqp.Client, storageApp absto.Storage, tracer trace.Tracer) App {
return App{
geocodeApp: geocodeApp,
storageApp: storageApp,
amqpClient: amqpClient,
amqpExchange: strings.TrimSpace(*config.amqpExchange),
amqpRoutingKey: strings.TrimSpace(*config.amqpRoutingKey),
tracer: tracer,
metric: prom.CounterVec(prometheusRegisterer, "exas", "", "item", "source", "kind", "state"),
}
}
// Handler for request. Should be use with net/http
func (a App) Handler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodPost:
a.handlePost(w, r)
case http.MethodGet:
a.handleGet(w, r)
default:
w.WriteHeader(http.StatusMethodNotAllowed)
}
})
}
func (a App) get(ctx context.Context, input io.Reader) (exif model.Exif, err error) {
ctx, end := tracer.StartSpan(ctx, a.tracer, "exiftool")
defer end(&err)
cmd := exec.Command("./exiftool", "-json", "-")
buffer := bufferPool.Get().(*bytes.Buffer)
defer bufferPool.Put(buffer)
buffer.Reset()
cmd.Stdin = input
cmd.Stdout = buffer
cmd.Stderr = buffer
if err := cmd.Run(); err != nil {
return exif, fmt.Errorf("extract exif `%s`: %w", buffer.String(), err)
}
var exifs []map[string]any
if err := json.NewDecoder(buffer).Decode(&exifs); err != nil {
return exif, fmt.Errorf("decode exiftool output: %w", err)
}
var exifData map[string]any
if len(exifs) > 0 {
exifData = exifs[0]
}
for key, value := range exifData {
if strValue, ok := value.(string); ok && strings.HasPrefix(strValue, "(Binary data") {
delete(exifData, key)
}
}
exif.Data = exifData
exif.Date = getDate(exif)
exif.Geocode, err = a.geocodeApp.GetGeocoding(ctx, exif)
if err != nil {
return exif, fmt.Errorf("append geocoding: %w", err)
}
return exif, nil
}