/
objectid.go
109 lines (85 loc) · 2.77 KB
/
objectid.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
package object
import (
"encoding/hex"
"strings"
"github.com/pkg/errors"
"github.com/kopia/kopia/repo/content"
)
// ID is an identifier of a repository object. Repository objects can be stored.
//
// 1. In a single content block, this is the most common case for small objects.
// 2. In a series of content blocks with an indirect block pointing at them (multiple indirections are allowed).
// This is used for larger files. Object IDs using indirect blocks start with "I"
type ID string
// HasObjectID exposes the identifier of an object.
type HasObjectID interface {
ObjectID() ID
}
// String returns string representation of ObjectID that is suitable for displaying in the UI.
func (i ID) String() string {
return strings.Replace(string(i), "D", "", -1)
}
// IndexObjectID returns the object ID of the underlying index object.
func (i ID) IndexObjectID() (ID, bool) {
if strings.HasPrefix(string(i), "I") {
return i[1:], true
}
return "", false
}
// ContentID returns the ID of the underlying content.
func (i ID) ContentID() (id content.ID, compressed, ok bool) {
if strings.HasPrefix(string(i), "D") {
return content.ID(i[1:]), false, true
}
if strings.HasPrefix(string(i), "I") {
return "", false, false
}
if strings.HasPrefix(string(i), "Z") {
return content.ID(i[1:]), true, true
}
return content.ID(i), false, true
}
// Validate checks the ID format for validity and reports any errors.
func (i ID) Validate() error {
if indexObjectID, ok := i.IndexObjectID(); ok {
if err := indexObjectID.Validate(); err != nil {
return errors.Wrapf(err, "invalid indirect object ID %v", i)
}
return nil
}
contentID, _, ok := i.ContentID()
if !ok {
return errors.Errorf("invalid object ID: %v", i)
}
if len(contentID) <= 1 {
return errors.Errorf("missing content ID")
}
// odd length - firstcharacter must be a single character between 'g' and 'z'
if len(contentID)%2 == 1 {
if contentID[0] < 'g' || contentID[0] > 'z' {
return errors.Errorf("invalid content ID prefix: %v", contentID)
}
contentID = contentID[1:]
}
if _, err := hex.DecodeString(string(contentID)); err != nil {
return errors.Errorf("invalid contentID suffix, must be base-16 encoded: %v", contentID)
}
return nil
}
// DirectObjectID returns direct object ID based on the provided block ID.
func DirectObjectID(contentID content.ID) ID {
return ID(contentID)
}
// Compressed returns object ID with 'Z' prefix indicating it's compressed.
func Compressed(objectID ID) ID {
return "Z" + objectID
}
// IndirectObjectID returns indirect object ID based on the underlying index object ID.
func IndirectObjectID(indexObjectID ID) ID {
return "I" + indexObjectID
}
// ParseID converts the specified string into object ID.
func ParseID(s string) (ID, error) {
i := ID(s)
return i, i.Validate()
}