diff --git a/src/cmd/tusd/chunk.go b/src/cmd/tusd/chunk.go new file mode 100644 index 0000000..f350ccc --- /dev/null +++ b/src/cmd/tusd/chunk.go @@ -0,0 +1,66 @@ +package main + +import ( + "sort" +) + +// chunk holds the offsets for a partial piece of data +type chunk struct { + Start int64 `json:"start"` + End int64 `json:"end"` +} + +// Size returns the number of bytes between Start and End. +func (c chunk) Size() int64 { + return c.End - c.Start + 1 +} + +// chunkSet holds a set of chunks and helps with adding/merging new chunks into +// set set. +type chunkSet []chunk + +// Add merges a newChunk into a chunkSet. This may lead to the chunk being +// combined with one or more adjecent chunks, possibly shrinking the chunkSet +// down to a single member. +func (c *chunkSet) Add(newChunk chunk) { + if newChunk.Size() <= 0 { + return + } + + *c = append(*c, newChunk) + sort.Sort(c) + + // merge chunks that can be combined + for i := 0; i < len(*c)-1; i++ { + current := (*c)[i] + next := (*c)[i+1] + + if current.End+1 < next.Start { + continue + } + + *c = append((*c)[0:i], (*c)[i+1:]...) + + if current.End > next.End { + (*c)[i].End = current.End + } + + if current.Start < next.Start { + (*c)[i].Start = current.Start + } + + i-- + } +} + +func (c chunkSet) Len() int { + return len(c) +} + +func (c chunkSet) Less(i, j int) bool { + return c[i].Start < c[j].Start +} + +func (c chunkSet) Swap(i, j int) { + c[i], c[j] = c[j], c[i] +} diff --git a/src/cmd/tusd/chunk_test.go b/src/cmd/tusd/chunk_test.go new file mode 100644 index 0000000..59eecc1 --- /dev/null +++ b/src/cmd/tusd/chunk_test.go @@ -0,0 +1,99 @@ +package main + +import ( + "fmt" + "testing" +) + +var chunkSet_AddTests = []struct { + Name string + Add []chunk + Expect []chunk +}{ + { + Name: "add one", + Add: []chunk{{Start: 1, End: 5}}, + Expect: []chunk{{Start: 1, End: 5}}, + }, + { + Name: "add twice", + Add: []chunk{{Start: 1, End: 5}, {Start: 1, End: 5}}, + Expect: []chunk{{Start: 1, End: 5}}, + }, + { + Name: "append", + Add: []chunk{{Start: 1, End: 5}, {Start: 7, End: 10}}, + Expect: []chunk{{Start: 1, End: 5}, {Start: 7, End: 10}}, + }, + { + Name: "insert", + Add: []chunk{{Start: 0, End: 5}, {Start: 12, End: 15}, {Start: 7, End: 10}}, + Expect: []chunk{{Start: 0, End: 5}, {Start: 7, End: 10}, {Start: 12, End: 15}}, + }, + { + Name: "prepend", + Add: []chunk{{Start: 5, End: 10}, {Start: 1, End: 3}}, + Expect: []chunk{{Start: 1, End: 3}, {Start: 5, End: 10}}, + }, + { + Name: "grow start", + Add: []chunk{{Start: 1, End: 5}, {Start: 0, End: 5}}, + Expect: []chunk{{Start: 0, End: 5}}, + }, + { + Name: "grow end", + Add: []chunk{{Start: 1, End: 5}, {Start: 1, End: 6}}, + Expect: []chunk{{Start: 1, End: 6}}, + }, + { + Name: "grow end with multiple items", + Add: []chunk{{Start: 1, End: 5}, {Start: 7, End: 10}, {Start: 8, End: 15}}, + Expect: []chunk{{Start: 1, End: 5}, {Start: 7, End: 15}}, + }, + { + Name: "grow exact end match", + Add: []chunk{{Start: 1, End: 5}, {Start: 6, End: 6}}, + Expect: []chunk{{Start: 1, End: 6}}, + }, + { + Name: "sink", + Add: []chunk{{Start: 1, End: 5}, {Start: 2, End: 3}}, + Expect: []chunk{{Start: 1, End: 5}}, + }, + { + Name: "swallow", + Add: []chunk{{Start: 1, End: 5}, {Start: 6, End: 10}, {Start: 0, End: 11}}, + Expect: []chunk{{Start: 0, End: 11}}, + }, + { + Name: "ignore 0 byte chunks", + Add: []chunk{{Start: 0, End: -1}}, + Expect: []chunk{}, + }, + { + Name: "ignore invalid chunks", + Add: []chunk{{Start: 0, End: -2}}, + Expect: []chunk{}, + }, +} + +func Test_chunkSet_Add(t *testing.T) { + for _, test := range chunkSet_AddTests { + var chunks chunkSet + for _, chunk := range test.Add { + chunks.Add(chunk) + } + + expected := fmt.Sprintf("%+v", test.Expect) + got := fmt.Sprintf("%+v", chunks) + + if got != expected { + t.Errorf( + "Failed test '%s':\nexpected: %s\ngot: %s", + test.Name, + expected, + got, + ) + } + } +}