mirror of
https://github.com/derfenix/photocatalog.git
synced 2026-03-11 21:35:34 +03:00
Full application rewrite
This commit is contained in:
53
internal/metadata/default.go
Normal file
53
internal/metadata/default.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const timeFormat = "20060102_150405"
|
||||
|
||||
var (
|
||||
ErrBadPrefix = errors.New("bad prefix")
|
||||
ErrBadNameFormat = errors.New("bad name format")
|
||||
)
|
||||
|
||||
type Default struct {
|
||||
TimeFormat string
|
||||
Prefix string
|
||||
}
|
||||
|
||||
func (d *Default) Extract(fp string, _ io.Reader) (Metadata, error) {
|
||||
format := d.TimeFormat
|
||||
if format == "" {
|
||||
format = timeFormat
|
||||
}
|
||||
|
||||
if d.Prefix != "" && !strings.HasPrefix(fp, d.Prefix) {
|
||||
return Metadata{}, fmt.Errorf("%w: expect a prefix %s, got %s", ErrBadPrefix, d.Prefix, fp)
|
||||
}
|
||||
|
||||
fp = filepath.Base(fp)
|
||||
|
||||
leftLimit := len(d.Prefix)
|
||||
rightLimit := leftLimit + len(format)
|
||||
|
||||
if len(fp) < rightLimit {
|
||||
return Metadata{}, fmt.Errorf("%w: too short", ErrBadNameFormat)
|
||||
}
|
||||
|
||||
created, err := time.Parse(format, fp[leftLimit:rightLimit])
|
||||
if err != nil {
|
||||
return Metadata{}, fmt.Errorf("parse time: %w", err)
|
||||
}
|
||||
|
||||
meta := Metadata{
|
||||
Created: created,
|
||||
}
|
||||
|
||||
return meta, nil
|
||||
}
|
||||
123
internal/metadata/default_test.go
Normal file
123
internal/metadata/default_test.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package metadata_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/derfenix/photocatalog/internal/metadata"
|
||||
)
|
||||
|
||||
func TestDefault_Extract(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const timeFormat = "20060102_150405"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
filePath string
|
||||
prefix string
|
||||
timeFormat string
|
||||
want Metadata
|
||||
wantErr bool
|
||||
wantErrType error
|
||||
}{
|
||||
{
|
||||
name: "simple",
|
||||
filePath: "20241108_160834.jpg",
|
||||
timeFormat: timeFormat,
|
||||
want: Metadata{
|
||||
Created: time.Date(2024, 11, 8, 16, 8, 34, 0, time.UTC),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "simple in subdirectory",
|
||||
filePath: "foo/20241108_160834.jpg",
|
||||
timeFormat: timeFormat,
|
||||
want: Metadata{
|
||||
Created: time.Date(2024, 11, 8, 16, 8, 34, 0, time.UTC),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "simple with suffix",
|
||||
filePath: "20241108_160834_(1).jpg",
|
||||
timeFormat: timeFormat,
|
||||
want: Metadata{
|
||||
Created: time.Date(2024, 11, 8, 16, 8, 34, 0, time.UTC),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "prefix",
|
||||
filePath: "foo_20241108_160834.jpg",
|
||||
timeFormat: timeFormat,
|
||||
prefix: "foo_",
|
||||
want: Metadata{
|
||||
Created: time.Date(2024, 11, 8, 16, 8, 34, 0, time.UTC),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "bad prefix",
|
||||
filePath: "foo_20241108_160834.jpg",
|
||||
timeFormat: timeFormat,
|
||||
prefix: "bar_",
|
||||
want: Metadata{
|
||||
Created: time.Time{},
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrType: ErrBadPrefix,
|
||||
},
|
||||
{
|
||||
name: "no prefix",
|
||||
filePath: "20241108_160834.jpg",
|
||||
timeFormat: timeFormat,
|
||||
prefix: "bar_",
|
||||
want: Metadata{
|
||||
Created: time.Time{},
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrType: ErrBadPrefix,
|
||||
},
|
||||
{
|
||||
name: "unexpected prefix",
|
||||
filePath: "foo_20241108_160834.jpg",
|
||||
timeFormat: timeFormat,
|
||||
prefix: "",
|
||||
want: Metadata{
|
||||
Created: time.Time{},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
d := &Default{
|
||||
TimeFormat: tt.timeFormat,
|
||||
Prefix: tt.prefix,
|
||||
}
|
||||
|
||||
got, err := d.Extract(tt.filePath, nil)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Extract() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
if tt.wantErr && tt.wantErrType != nil {
|
||||
if !errors.Is(err, tt.wantErrType) {
|
||||
t.Errorf("Extract() errorType = %v, wantErrType %v", err, tt.wantErrType)
|
||||
}
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Extract() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
28
internal/metadata/exif.go
Normal file
28
internal/metadata/exif.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/rwcarlsen/goexif/exif"
|
||||
)
|
||||
|
||||
type Exif struct{}
|
||||
|
||||
func (j Exif) Extract(_ string, data io.Reader) (Metadata, error) {
|
||||
decode, err := exif.Decode(data)
|
||||
if err != nil {
|
||||
return Metadata{}, fmt.Errorf("decode exif: %w", err)
|
||||
}
|
||||
|
||||
meta := Metadata{}
|
||||
|
||||
created, err := decode.DateTime()
|
||||
if err != nil {
|
||||
return Metadata{}, fmt.Errorf("parse datetime: %w", err)
|
||||
}
|
||||
|
||||
meta.Created = created
|
||||
|
||||
return meta, nil
|
||||
}
|
||||
9
internal/metadata/metadata.go
Normal file
9
internal/metadata/metadata.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Metadata struct {
|
||||
Created time.Time
|
||||
}
|
||||
Reference in New Issue
Block a user