Skip to content

Commit 69821c4

Browse files
committed
value_context.go: ValueContext now embeds value processing
The upshot of this is that generating a list of type-correct slices or a string from a tag's value is now stupidly easy. - Broke parsing into an independent type (parser.go:Parser). - Moved primary value-processing logic to `ValueContext` so that it coexists with the actual data (though as much basic functionality as possible is implemented independently and reused here). This eliminates extremely ridiculously obtuse usage procedure. - Deprecated almost all existing TagType functionality (in order to distance us from this now-legacy usage pattern). Existing functionality maintained for now. We'll drop it when we do the next release major. - `ValueContext` is now passed by reference.
1 parent 7fb09bb commit 69821c4

10 files changed

+1013
-680
lines changed

exif-read-tool/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ func main() {
117117
valueString = fmt.Sprintf("%v", value)
118118
}
119119
} else {
120-
valueString, err = tagType.ResolveAsString(valueContext, true)
120+
valueString, err = valueContext.FormatFirst()
121121
log.PanicIf(err)
122122

123123
value = valueString

exif_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ func TestVisit(t *testing.T) {
9090
valueString = fmt.Sprintf("%v", value)
9191
}
9292
} else {
93-
valueString, err = tagType.ResolveAsString(valueContext, true)
93+
valueString, err = valueContext.FormatFirst()
9494
log.PanicIf(err)
9595
}
9696

ifd_builder.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,9 @@ func (bt *BuilderTag) String() string {
129129
var valueString string
130130

131131
if bt.value.IsBytes() == true {
132-
tt := NewTagType(bt.typeId, bt.byteOrder)
133-
134132
var err error
135133

136-
valueString, err = tt.Format(bt.value.Bytes(), false)
134+
valueString, err = Format(bt.value.Bytes(), bt.typeId, false, bt.byteOrder)
137135
log.PanicIf(err)
138136
} else {
139137
valueString = fmt.Sprintf("%v", bt.value)

ifd_enumerate.go

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -294,20 +294,59 @@ func (ie *IfdEnumerate) resolveTagValue(ite *IfdTagEntry) (valueBytes []byte, is
294294
return valueBytes, false, nil
295295
}
296296

297+
// RawTagVisitorPtr is an optional callback that can get hit for every tag we parse
298+
// through. `addressableData` is the byte array startign after the EXIF header
299+
// (where the offsets of all IFDs and values are calculated from).
300+
//
301+
// This was reimplemented as an interface to allow for simpler change management
302+
// in the future.
303+
type RawTagWalk interface {
304+
Visit(fqIfdPath string, ifdIndex int, tagId uint16, tagType TagType, valueContext *ValueContext) (err error)
305+
}
306+
307+
type RawTagWalkLegacyWrapper struct {
308+
legacyVisitor RawTagVisitor
309+
}
310+
311+
func (rtwlw RawTagWalkLegacyWrapper) Visit(fqIfdPath string, ifdIndex int, tagId uint16, tagType TagType, valueContext *ValueContext) (err error) {
312+
return rtwlw.legacyVisitor(fqIfdPath, ifdIndex, tagId, tagType, *valueContext)
313+
}
314+
297315
// RawTagVisitor is an optional callback that can get hit for every tag we parse
298316
// through. `addressableData` is the byte array startign after the EXIF header
299317
// (where the offsets of all IFDs and values are calculated from).
318+
//
319+
// DEPRECATED(dustin): Use a RawTagWalk instead.
300320
type RawTagVisitor func(fqIfdPath string, ifdIndex int, tagId uint16, tagType TagType, valueContext ValueContext) (err error)
301321

302322
// ParseIfd decodes the IFD block that we're currently sitting on the first
303323
// byte of.
304-
func (ie *IfdEnumerate) ParseIfd(fqIfdPath string, ifdIndex int, ite *IfdTagEnumerator, visitor RawTagVisitor, doDescend bool, resolveValues bool) (nextIfdOffset uint32, entries []*IfdTagEntry, thumbnailData []byte, err error) {
324+
func (ie *IfdEnumerate) ParseIfd(fqIfdPath string, ifdIndex int, ite *IfdTagEnumerator, visitor interface{}, doDescend bool, resolveValues bool) (nextIfdOffset uint32, entries []*IfdTagEntry, thumbnailData []byte, err error) {
305325
defer func() {
306326
if state := recover(); state != nil {
307327
err = log.Wrap(state.(error))
308328
}
309329
}()
310330

331+
var visitorWrapper RawTagWalk
332+
333+
if visitor != nil {
334+
var ok bool
335+
336+
visitorWrapper, ok = visitor.(RawTagWalk)
337+
if ok == false {
338+
// Legacy usage.
339+
340+
// `ok` can be `true` but `legacyVisitor` can still be `nil` (when
341+
// passed as nil).
342+
if legacyVisitor, ok := visitor.(RawTagVisitor); ok == true && legacyVisitor != nil {
343+
visitorWrapper = RawTagWalkLegacyWrapper{
344+
legacyVisitor: legacyVisitor,
345+
}
346+
}
347+
}
348+
}
349+
311350
tagCount, _, err := ite.getUint16()
312351
log.PanicIf(err)
313352

@@ -338,7 +377,7 @@ func (ie *IfdEnumerate) ParseIfd(fqIfdPath string, ifdIndex int, ite *IfdTagEnum
338377
continue
339378
}
340379

341-
if visitor != nil {
380+
if visitorWrapper != nil {
342381
tt := NewTagType(tag.TagType, ie.byteOrder)
343382

344383
vc :=
@@ -350,7 +389,7 @@ func (ie *IfdEnumerate) ParseIfd(fqIfdPath string, ifdIndex int, ite *IfdTagEnum
350389
tag.TagType,
351390
ie.byteOrder)
352391

353-
err := visitor(fqIfdPath, ifdIndex, tag.TagId, tt, vc)
392+
err := visitorWrapper.Visit(fqIfdPath, ifdIndex, tag.TagId, tt, vc)
354393
log.PanicIf(err)
355394
}
356395

@@ -412,7 +451,7 @@ func (ie *IfdEnumerate) parseThumbnail(offsetIte, lengthIte *IfdTagEntry) (thumb
412451
}
413452

414453
// Scan enumerates the different EXIF's IFD blocks.
415-
func (ie *IfdEnumerate) scan(fqIfdName string, ifdOffset uint32, visitor RawTagVisitor, resolveValues bool) (err error) {
454+
func (ie *IfdEnumerate) scan(fqIfdName string, ifdOffset uint32, visitor interface{}, resolveValues bool) (err error) {
416455
defer func() {
417456
if state := recover(); state != nil {
418457
err = log.Wrap(state.(error))

ifd_tag_entry.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,7 @@ func (ite IfdTagEntry) ValueString(addressableData []byte, byteOrder binary.Byte
7070

7171
value = fmt.Sprintf("%v", valueRaw)
7272
} else {
73-
tt := NewTagType(ite.TagType, byteOrder)
74-
75-
value, err = tt.ResolveAsString(vc, false)
73+
value, err = vc.Format()
7674
log.PanicIf(err)
7775
}
7876

parser.go

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
package exif
2+
3+
import (
4+
"bytes"
5+
6+
"encoding/binary"
7+
8+
"github.com/dsoprea/go-logging"
9+
)
10+
11+
type Parser struct {
12+
}
13+
14+
func (p *Parser) ParseBytes(data []byte, unitCount uint32) (value []uint8, err error) {
15+
defer func() {
16+
if state := recover(); state != nil {
17+
err = log.Wrap(state.(error))
18+
}
19+
}()
20+
21+
count := int(unitCount)
22+
23+
if len(data) < (TypeByte.Size() * count) {
24+
log.Panic(ErrNotEnoughData)
25+
}
26+
27+
value = []uint8(data[:count])
28+
29+
return value, nil
30+
}
31+
32+
// ParseAscii returns a string and auto-strips the trailing NUL character.
33+
func (p *Parser) ParseAscii(data []byte, unitCount uint32) (value string, err error) {
34+
defer func() {
35+
if state := recover(); state != nil {
36+
err = log.Wrap(state.(error))
37+
}
38+
}()
39+
40+
count := int(unitCount)
41+
42+
if len(data) < (TypeAscii.Size() * count) {
43+
log.Panic(ErrNotEnoughData)
44+
}
45+
46+
if len(data) == 0 || data[count-1] != 0 {
47+
s := string(data[:count])
48+
typeLogger.Warningf(nil, "ascii not terminated with nul as expected: [%v]", s)
49+
50+
return s, nil
51+
} else {
52+
// Auto-strip the NUL from the end. It serves no purpose outside of
53+
// encoding semantics.
54+
55+
return string(data[:count-1]), nil
56+
}
57+
}
58+
59+
// ParseAsciiNoNul returns a string without any consideration for a trailing NUL
60+
// character.
61+
func (p *Parser) ParseAsciiNoNul(data []byte, unitCount uint32) (value string, err error) {
62+
defer func() {
63+
if state := recover(); state != nil {
64+
err = log.Wrap(state.(error))
65+
}
66+
}()
67+
68+
count := int(unitCount)
69+
70+
if len(data) < (TypeAscii.Size() * count) {
71+
log.Panic(ErrNotEnoughData)
72+
}
73+
74+
return string(data[:count]), nil
75+
}
76+
77+
func (p *Parser) ParseShorts(data []byte, unitCount uint32, byteOrder binary.ByteOrder) (value []uint16, err error) {
78+
defer func() {
79+
if state := recover(); state != nil {
80+
err = log.Wrap(state.(error))
81+
}
82+
}()
83+
84+
count := int(unitCount)
85+
86+
if len(data) < (TypeShort.Size() * count) {
87+
log.Panic(ErrNotEnoughData)
88+
}
89+
90+
value = make([]uint16, count)
91+
for i := 0; i < count; i++ {
92+
value[i] = byteOrder.Uint16(data[i*2:])
93+
}
94+
95+
return value, nil
96+
}
97+
98+
func (p *Parser) ParseLongs(data []byte, unitCount uint32, byteOrder binary.ByteOrder) (value []uint32, err error) {
99+
defer func() {
100+
if state := recover(); state != nil {
101+
err = log.Wrap(state.(error))
102+
}
103+
}()
104+
105+
count := int(unitCount)
106+
107+
if len(data) < (TypeLong.Size() * count) {
108+
log.Panic(ErrNotEnoughData)
109+
}
110+
111+
value = make([]uint32, count)
112+
for i := 0; i < count; i++ {
113+
value[i] = byteOrder.Uint32(data[i*4:])
114+
}
115+
116+
return value, nil
117+
}
118+
119+
func (p *Parser) ParseRationals(data []byte, unitCount uint32, byteOrder binary.ByteOrder) (value []Rational, err error) {
120+
defer func() {
121+
if state := recover(); state != nil {
122+
err = log.Wrap(state.(error))
123+
}
124+
}()
125+
126+
count := int(unitCount)
127+
128+
if len(data) < (TypeRational.Size() * count) {
129+
log.Panic(ErrNotEnoughData)
130+
}
131+
132+
value = make([]Rational, count)
133+
for i := 0; i < count; i++ {
134+
value[i].Numerator = byteOrder.Uint32(data[i*8:])
135+
value[i].Denominator = byteOrder.Uint32(data[i*8+4:])
136+
}
137+
138+
return value, nil
139+
}
140+
141+
func (p *Parser) ParseSignedLongs(data []byte, unitCount uint32, byteOrder binary.ByteOrder) (value []int32, err error) {
142+
defer func() {
143+
if state := recover(); state != nil {
144+
err = log.Wrap(state.(error))
145+
}
146+
}()
147+
148+
count := int(unitCount)
149+
150+
if len(data) < (TypeSignedLong.Size() * count) {
151+
log.Panic(ErrNotEnoughData)
152+
}
153+
154+
b := bytes.NewBuffer(data)
155+
156+
value = make([]int32, count)
157+
for i := 0; i < count; i++ {
158+
err := binary.Read(b, byteOrder, &value[i])
159+
log.PanicIf(err)
160+
}
161+
162+
return value, nil
163+
}
164+
165+
func (p *Parser) ParseSignedRationals(data []byte, unitCount uint32, byteOrder binary.ByteOrder) (value []SignedRational, err error) {
166+
defer func() {
167+
if state := recover(); state != nil {
168+
err = log.Wrap(state.(error))
169+
}
170+
}()
171+
172+
count := int(unitCount)
173+
174+
if len(data) < (TypeSignedRational.Size() * count) {
175+
log.Panic(ErrNotEnoughData)
176+
}
177+
178+
b := bytes.NewBuffer(data)
179+
180+
value = make([]SignedRational, count)
181+
for i := 0; i < count; i++ {
182+
err = binary.Read(b, byteOrder, &value[i].Numerator)
183+
log.PanicIf(err)
184+
185+
err = binary.Read(b, byteOrder, &value[i].Denominator)
186+
log.PanicIf(err)
187+
}
188+
189+
return value, nil
190+
}

0 commit comments

Comments
 (0)