kill the queue
This commit is contained in:
parent
069e96f179
commit
0e217dffbf
@ -1 +0,0 @@
|
||||
language: go
|
@ -1,56 +0,0 @@
|
||||
# Simple UUIDs
|
||||
|
||||
[![Build Status][1]][2]
|
||||
|
||||
[1]: https://secure.travis-ci.org/streadway/simpleuuid.png
|
||||
[2]: http://www.travis-ci.org/streadway/simpleuuid
|
||||
|
||||
This implements a variant of Format 1 from [RFC 4122][rfc4122] that is intended
|
||||
to be roughly sortable and play nicely as Cassandra TimeUUID keys. Much of the
|
||||
inspiration comes form having used [ryanking's simple\_uuid
|
||||
gem](https://github.com/ryanking/simple_uuid).
|
||||
|
||||
As the package implies, this is a simple UUID generator. It doesn't offer
|
||||
total sorting, monotonic increments or prevent timing collisions.
|
||||
|
||||
This package does offer a simple combination of random and time based
|
||||
uniqueness that will play nicely if you want a unique key from a Time object.
|
||||
If your time objects sort with a granularity of 100 nanoseconds then the UUIDs
|
||||
generated will have the same order. UUIDs with the same time have undefined
|
||||
order.
|
||||
|
||||
# Other UUIDs
|
||||
|
||||
The other formats described in [RFC 4122][rfc4122] should be parsable either in
|
||||
text or byte form, though will not be sortable or likely have a meaningful time
|
||||
componenet.
|
||||
|
||||
# Contributions
|
||||
|
||||
Send a pull request with a tested `go fmt` change in a branch other than
|
||||
`master`. Do try to organize your commits to be atomic to the changes
|
||||
introduced.
|
||||
|
||||
# License
|
||||
|
||||
Copyright (C) 2012 by Sean Treadway ([streadway](http://github.com/streadway))
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
[rfc4122]: http://www.ietf.org/rfc/rfc4122.txt
|
@ -1,263 +0,0 @@
|
||||
/*
|
||||
Copyright (C) 2012 by Sean Treadway ([streadway](http://github.com/streadway))
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
Implements a variant of Format 1 from RFC 4122 that is intended for stable
|
||||
sorting and play nicely as Cassandra TimeUUID keys.
|
||||
|
||||
The other formats described in RFC 4122 should be parsable either in text or
|
||||
byte form, though will not be sortable or likely have a meaningful time
|
||||
componenet.
|
||||
*/
|
||||
package simpleuuid
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
gregorianEpoch = 0x01B21DD213814000
|
||||
size = 16
|
||||
variant8 = 8 // sec. 4.1.1
|
||||
version1 = 1 // sec. 4.1.3
|
||||
)
|
||||
|
||||
var (
|
||||
errLength = errors.New("mismatched UUID length")
|
||||
)
|
||||
|
||||
/*
|
||||
Byte encoded sequence in the following form:
|
||||
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| time_low |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| time_mid | time_hi_and_version |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|clk_seq_hi_res | clk_seq_low | node (0-1) |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| node (2-5) |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
*/
|
||||
type UUID []byte
|
||||
|
||||
type uuidTime int64
|
||||
|
||||
// Makes a copy of the UUID. Assumes the provided UUID is valid
|
||||
func Copy(uuid UUID) UUID {
|
||||
dup, _ := NewBytes(uuid)
|
||||
return dup
|
||||
}
|
||||
|
||||
// Allocates a new UUID from the given time, up to 8 bytes clock sequence and
|
||||
// node data supplied by the caller. The high 4 bits of the first byte will be
|
||||
// masked with the UUID variant.
|
||||
//
|
||||
// Byte slices shorter than 8 will be right aligned to the clock, and node
|
||||
// fields. For example, if you provide 4 byte slice of 0x0a0b0c0d", the last 8
|
||||
// bytes of the new UUID will be 0x08000000a0b0c0d.
|
||||
func NewTimeBytes(t time.Time, bytes []byte) (UUID, error) {
|
||||
if len(bytes) > size {
|
||||
return nil, errLength
|
||||
}
|
||||
|
||||
me := make([]byte, size)
|
||||
ts := fromUnixNano(t.UTC().UnixNano())
|
||||
|
||||
// time masked with version
|
||||
binary.BigEndian.PutUint32(me[0:4], uint32(ts&0xffffffff))
|
||||
binary.BigEndian.PutUint16(me[4:6], uint16((ts>>32)&0xffff))
|
||||
binary.BigEndian.PutUint16(me[6:8], uint16((ts>>48)&0x0fff)|version1<<12)
|
||||
|
||||
// right aligned remaining 8 bytes masked with variant
|
||||
copy(me[8+8-len(bytes):size], bytes[:len(bytes)])
|
||||
me[8] = me[8]&0x0f | variant8<<4
|
||||
|
||||
return UUID(me), nil
|
||||
}
|
||||
|
||||
// Allocate a UUID from a 16 byte sequence. This can take any version,
|
||||
// although versions other than 1 will not have a meaningful time component.
|
||||
func NewBytes(bytes []byte) (UUID, error) {
|
||||
if len(bytes) != size {
|
||||
return nil, errLength
|
||||
}
|
||||
|
||||
// Copy out this slice so not to hold a reference to the container
|
||||
b := make([]byte, size)
|
||||
copy(b, bytes[0:size])
|
||||
|
||||
return UUID(b), nil
|
||||
}
|
||||
|
||||
// Allocate a new UUID from a time, encoding the timestamp from the UTC
|
||||
// timezone and using a random value for the clock and node.
|
||||
func NewTime(t time.Time) (UUID, error) {
|
||||
rnd := make([]byte, 8)
|
||||
n, err := io.ReadFull(rand.Reader, rnd)
|
||||
if n != len(rnd) {
|
||||
return nil, errLength
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewTimeBytes(t, rnd)
|
||||
}
|
||||
|
||||
// Parse and allocate from a string encoded UUID like:
|
||||
// "6ba7b811-9dad-11d1-80b4-00c04fd430c8". Does not validate the time, node
|
||||
// or clock are reasonable values, though it is intended to round trip from a
|
||||
// string to a string for all versions of UUIDs.
|
||||
func NewString(s string) (UUID, error) {
|
||||
normalized := strings.Replace(s, "-", "", -1)
|
||||
|
||||
if hex.DecodedLen(len(normalized)) != size {
|
||||
return nil, errLength
|
||||
}
|
||||
|
||||
bytes, err := hex.DecodeString(normalized)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return UUID(bytes), nil
|
||||
}
|
||||
|
||||
// The time section of the UUID in the UTC timezone
|
||||
func (me UUID) Time() time.Time {
|
||||
nsec := me.Nanoseconds()
|
||||
return time.Unix(nsec/1e9, nsec%1e9).UTC()
|
||||
}
|
||||
|
||||
// Returns the time_low, time_mid and time_hi sections of the UUID in 100
|
||||
// nanosecond resolution since the unix Epoch. Negative values indicate
|
||||
// time prior to the gregorian epoch (00:00:00.00, 15 October 1582).
|
||||
func (me UUID) Nanoseconds() int64 {
|
||||
time_low := uuidTime(binary.BigEndian.Uint32(me[0:4]))
|
||||
time_mid := uuidTime(binary.BigEndian.Uint16(me[4:6]))
|
||||
time_hi := uuidTime((binary.BigEndian.Uint16(me[6:8]) & 0x0fff))
|
||||
|
||||
return toUnixNano((time_low) + (time_mid << 32) + (time_hi << 48))
|
||||
}
|
||||
|
||||
/*
|
||||
The 4 bit version of the underlying UUID.
|
||||
|
||||
Version Description
|
||||
1 The time-based version specified in RFC4122.
|
||||
|
||||
2 DCE Security version, with embedded POSIX UIDs.
|
||||
|
||||
3 The name-based version specified in RFC4122
|
||||
that uses MD5 hashing.
|
||||
|
||||
4 The randomly or pseudo- randomly generated version
|
||||
specified in RFC4122.
|
||||
|
||||
5 The name-based version specified in RFC4122
|
||||
that uses SHA-1 hashing.
|
||||
*/
|
||||
func (me UUID) Version() int8 {
|
||||
return int8((binary.BigEndian.Uint16(me[6:8]) & 0xf000) >> 12)
|
||||
}
|
||||
|
||||
/*
|
||||
The 3 bit variant of the underlying UUID.
|
||||
|
||||
Msb0 Msb1 Msb2 Description
|
||||
0 x x Reserved, NCS backward compatibility.
|
||||
1 0 x The variant specified in RFC4122.
|
||||
1 1 0 Reserved, Microsoft Corporation backward compatibility
|
||||
1 1 1 Reserved for future definition.
|
||||
*/
|
||||
func (me UUID) Variant() int8 {
|
||||
return int8((binary.BigEndian.Uint16(me[8:10]) & 0xe000) >> 13)
|
||||
}
|
||||
|
||||
// The timestamp in hex encoded form.
|
||||
func (me UUID) String() string {
|
||||
return hex.EncodeToString(me[0:4]) + "-" +
|
||||
hex.EncodeToString(me[4:6]) + "-" +
|
||||
hex.EncodeToString(me[6:8]) + "-" +
|
||||
hex.EncodeToString(me[8:10]) + "-" +
|
||||
hex.EncodeToString(me[10:16])
|
||||
}
|
||||
|
||||
// Stable comparison, first of the times then of the node values.
|
||||
func (me UUID) Compare(other UUID) int {
|
||||
nsMe := me.Nanoseconds()
|
||||
nsOther := other.Nanoseconds()
|
||||
if nsMe > nsOther {
|
||||
return 1
|
||||
} else if nsMe < nsOther {
|
||||
return -1
|
||||
}
|
||||
return bytes.Compare(me[8:], other[8:])
|
||||
}
|
||||
|
||||
// The underlying byte slice. Treat the slice returned as immutable.
|
||||
func (me UUID) Bytes() []byte {
|
||||
return me
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
func (me *UUID) UnmarshalJSON(b []byte) error {
|
||||
var field string
|
||||
if err := json.Unmarshal(b, &field); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
uuid, err := NewString(field)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*me = uuid
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (me UUID) MarshalJSON() ([]byte, error) {
|
||||
return []byte(`"` + me.String() + `"`), nil
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
|
||||
func fromUnixNano(ns int64) uuidTime {
|
||||
return uuidTime((ns / 100) + gregorianEpoch)
|
||||
}
|
||||
|
||||
func toUnixNano(t uuidTime) int64 {
|
||||
return int64((t - gregorianEpoch) * 100)
|
||||
}
|
@ -1,398 +0,0 @@
|
||||
/*
|
||||
Copyright (C) 2012 by Sean Treadway ([streadway](http://github.com/streadway))
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
package simpleuuid
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"testing/quick"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
zero = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
url = []byte{0x6b, 0xa7, 0xb8, 0x11, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8}
|
||||
urlString = "6ba7b811-9dad-11d1-80b4-00c04fd430c8"
|
||||
)
|
||||
|
||||
func TestNewBytes(t *testing.T) {
|
||||
_, err := NewBytes(zero)
|
||||
if err != nil {
|
||||
t.Error("Fail", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewTimeRoundTrip(t *testing.T) {
|
||||
now := time.Now()
|
||||
|
||||
uuid, err := NewTime(now)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
then := uuid.Time()
|
||||
if now.UnixNano()/100 != then.UnixNano()/100 {
|
||||
t.Errorf("UUID should parse and generate time based with 100ns precision. want %v, got %v", now.UTC(), then)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewString(t *testing.T) {
|
||||
uuid1, err := NewString(urlString)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if uuid1.String() != urlString {
|
||||
t.Error("Strings do not match", uuid1.String(), urlString)
|
||||
}
|
||||
|
||||
uuid2, err := NewString(strings.Replace(urlString, "-", "", -1))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if uuid2.String() != uuid1.String() {
|
||||
t.Error("Stripping dashes should not affect string parsing", uuid1, uuid2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBadNewString(t *testing.T) {
|
||||
_, err := NewString("0000")
|
||||
if err == nil {
|
||||
t.Error("Should fail on short GUID")
|
||||
}
|
||||
|
||||
_, err = NewString("00000000000000000000000000000000000000000")
|
||||
if err == nil {
|
||||
t.Error("Should fail on long GUID")
|
||||
}
|
||||
|
||||
_, err = NewString("0000------------------------0000")
|
||||
if err == nil {
|
||||
t.Error("Should fail on missing digits")
|
||||
}
|
||||
|
||||
_, err = NewString("-0--000-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0--0--")
|
||||
if err != nil {
|
||||
t.Error("Should ignore dashes")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestFormatString(t *testing.T) {
|
||||
uuid, err := NewBytes(url)
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if uuid.String() != urlString {
|
||||
t.Error("UUID should have correct string", uuid.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersion(t *testing.T) {
|
||||
url, err := NewBytes(url)
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if url.Version() != 0x1 {
|
||||
t.Error("Not recognized as a url version", url.Version())
|
||||
}
|
||||
|
||||
time, err := NewTime(time.Now())
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if time.Version() != 0x1 {
|
||||
t.Error("Not recognized as a time version", url.Version())
|
||||
}
|
||||
}
|
||||
|
||||
func TestVariant(t *testing.T) {
|
||||
url, err := NewBytes(url)
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if url.Variant() != 0x4 {
|
||||
t.Error("Variant should be 4", url.Variant())
|
||||
}
|
||||
|
||||
time, err := NewTime(time.Now())
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if time.Variant() != 0x4 {
|
||||
t.Error("Variant should be 4", url.Variant())
|
||||
}
|
||||
}
|
||||
|
||||
func TestBytes(t *testing.T) {
|
||||
url1, err := NewBytes(url)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
url2, err := NewBytes(url1.Bytes())
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if url1.String() != url2.String() {
|
||||
t.Error("Bytes not equal", url1, url2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalJSON(t *testing.T) {
|
||||
b := fmt.Sprintf(`{"uuid":"%s"}`, urlString)
|
||||
s := new(struct{ Uuid UUID })
|
||||
|
||||
if err := json.Unmarshal([]byte(b), s); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
got := s.Uuid.String()
|
||||
want := urlString
|
||||
|
||||
if got != want {
|
||||
t.Errorf("UUID Mismatch: %s, %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalJSON(t *testing.T) {
|
||||
uuid, err := NewString(urlString)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
b, err := json.Marshal(struct{ Uuid UUID }{uuid})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
got := string(b)
|
||||
want := fmt.Sprintf(`{"Uuid":"%s"}`, urlString)
|
||||
|
||||
if got != want {
|
||||
t.Errorf("Output mismatch: %s, %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompare(t *testing.T) {
|
||||
u1, err := NewBytes(url)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
u2, err := NewBytes(url)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
u3, err := NewBytes(zero)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if bytes.Compare(u1, u2) != 0 {
|
||||
t.Error("Should be equal", u1, u2)
|
||||
}
|
||||
|
||||
if bytes.Compare(u1, u3) <= 0 {
|
||||
t.Error("Should be greater", u1, u3)
|
||||
}
|
||||
|
||||
if bytes.Compare(u3, u1) >= 0 {
|
||||
t.Error("Should be less", u1, u3)
|
||||
}
|
||||
}
|
||||
|
||||
// Conditions
|
||||
|
||||
func TestUnixTimeAt100NanoResolution(t *testing.T) {
|
||||
f := func(sec, nsec uint32) bool {
|
||||
now := time.Unix(int64(sec), int64(nsec))
|
||||
u1, _ := NewTime(now)
|
||||
|
||||
return u1.Time().UnixNano()/100 == now.UnixNano()/100
|
||||
}
|
||||
|
||||
if err := quick.Check(f, nil); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInequalityForTimeWithRandom(t *testing.T) {
|
||||
f := func(sec, nsec uint32) bool {
|
||||
time := time.Unix(int64(sec), int64(nsec))
|
||||
u1, _ := NewTime(time)
|
||||
u2, _ := NewTime(time)
|
||||
|
||||
return u1.Compare(u2) != 0
|
||||
}
|
||||
|
||||
if err := quick.Check(f, nil); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEqualityForTimeWithBytes(t *testing.T) {
|
||||
f := func(sec, nsec uint32, b byte) bool {
|
||||
time := time.Unix(int64(sec), int64(nsec))
|
||||
u1, _ := NewTimeBytes(time, []byte{b, b, b, b, b, b, b, b})
|
||||
u2, _ := NewTimeBytes(time, []byte{b, b, b, b, b, b, b, b})
|
||||
|
||||
return u1.Compare(u2) == 0
|
||||
}
|
||||
|
||||
if err := quick.Check(f, nil); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoundTripOfTimeBytes(t *testing.T) {
|
||||
f := func(sec, nsec uint32, b byte) bool {
|
||||
time := time.Unix(int64(sec), int64(nsec))
|
||||
bs := []byte{b, b, b, b, b, b, b, b}
|
||||
|
||||
ut, _ := NewTimeBytes(time, bs)
|
||||
ub, _ := NewBytes([]byte(ut))
|
||||
|
||||
return bytes.Compare(ut[8:], ub[8:]) == 0
|
||||
}
|
||||
|
||||
if err := quick.Check(f, nil); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestZeroPaddedRightAlignmentOfTimeBytes(t *testing.T) {
|
||||
f := func(b byte, l uint) bool {
|
||||
bs := make([]byte, (l%8)+1)
|
||||
for i, _ := range bs {
|
||||
bs[i] = b
|
||||
}
|
||||
|
||||
u, err := NewTimeBytes(time.Now(), bs)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// check masked bytes right to left
|
||||
for i, j := 15, len(bs)-1; i >= 8; i, j = i-1, j-1 {
|
||||
if j >= 0 {
|
||||
if bs[j]&0x0f != u[i]&0x0f {
|
||||
t.Log("expected right aligned %d to equal %d", j, i)
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
if u[i]&0x0f != 0 {
|
||||
t.Log("expected right %d be zero", i)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if err := quick.Check(f, nil); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPositiveTime(t *testing.T) {
|
||||
f := func(sec, nsec uint32) bool {
|
||||
time := time.Unix(int64(sec), int64(nsec))
|
||||
u1, _ := NewTime(time)
|
||||
|
||||
return u1.Nanoseconds() > 0
|
||||
}
|
||||
|
||||
if err := quick.Check(f, nil); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrdering(t *testing.T) {
|
||||
f := func(sec1, nsec1, sec2, nsec2 uint32) bool {
|
||||
time1 := time.Unix(int64(sec1), int64(nsec1))
|
||||
time2 := time.Unix(int64(sec2), int64(nsec2))
|
||||
|
||||
u1, _ := NewTime(time1)
|
||||
u2, _ := NewTime(time2)
|
||||
|
||||
if time1.UnixNano() > time2.UnixNano() {
|
||||
return u1.Compare(u2) > 0
|
||||
}
|
||||
return u1.Compare(u2) < 0
|
||||
}
|
||||
|
||||
if err := quick.Check(f, nil); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Version 1 + Variant 8
|
||||
// xxxxxxxx-xxxx-1xxx-yxxx-xxxxxxxxxxxx y::{8 9 a b}
|
||||
// 012345678901234567890123456789012345
|
||||
func hasVersionAndVariantDigitsInString(u UUID) bool {
|
||||
s := u.String()
|
||||
return s[14] == '1' &&
|
||||
(s[19] == '8' ||
|
||||
s[19] == '9' ||
|
||||
s[19] == 'a' ||
|
||||
s[19] == 'b')
|
||||
}
|
||||
|
||||
func TestStringVersionAndVariantForNewTime(t *testing.T) {
|
||||
f := func(sec, nsec uint32) bool {
|
||||
u, _ := NewTime(time.Unix(int64(sec), int64(nsec)))
|
||||
return hasVersionAndVariantDigitsInString(u)
|
||||
}
|
||||
|
||||
if err := quick.Check(f, nil); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringVersionAndVariantForNewTimeBytes(t *testing.T) {
|
||||
f := func(sec, nsec uint32, b byte) bool {
|
||||
u, _ := NewTimeBytes(time.Unix(int64(sec), int64(nsec)), []byte{b, b, b, b, b, b, b, b})
|
||||
return hasVersionAndVariantDigitsInString(u)
|
||||
}
|
||||
|
||||
if err := quick.Check(f, nil); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
1
Godeps
1
Godeps
@ -5,5 +5,4 @@ github.com/kr/text 6807e777504f54ad073ecef66747de1582
|
||||
github.com/olekukonko/ts ecf753e7c962639ab5a1fb46f7da627d4c0a04b8
|
||||
github.com/spf13/cobra 864687ae689edc28688c67edef47e3d2ad651a1b
|
||||
github.com/spf13/pflag 463bdc838f2b35e9307e91d480878bda5fff7232
|
||||
github.com/streadway/simpleuuid 6617b501e485b77e61b98cd533aefff9e258b5a7
|
||||
github.com/technoweenie/go-contentaddressable 38171def3cd15e3b76eb156219b3d48704643899
|
||||
|
@ -1,44 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/github/git-media/gitmedia"
|
||||
"github.com/github/git-media/queuedir"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
queuesCmd = &cobra.Command{
|
||||
Use: "queues",
|
||||
Short: "Show the status of the internal Git Media queues",
|
||||
Run: queuesCommand,
|
||||
}
|
||||
)
|
||||
|
||||
func queuesCommand(cmd *cobra.Command, args []string) {
|
||||
err := gitmedia.WalkQueues(func(name string, queue *queuedir.Queue) error {
|
||||
wd, _ := os.Getwd()
|
||||
Print(name)
|
||||
return queue.Walk(func(id string, body []byte) error {
|
||||
parts := strings.Split(string(body), ":")
|
||||
if len(parts) == 2 {
|
||||
absPath := filepath.Join(gitmedia.LocalWorkingDir, parts[1])
|
||||
relPath, _ := filepath.Rel(wd, absPath)
|
||||
Print(" " + relPath)
|
||||
} else {
|
||||
Print(" " + parts[0])
|
||||
}
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
Panic(err, "Error walking queues")
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(queuesCmd)
|
||||
}
|
@ -91,13 +91,12 @@ Now, add a file:
|
||||
```
|
||||
$ git add my.zip
|
||||
|
||||
# confirm the zip was added to the "upload" queue
|
||||
$ git media queues
|
||||
upload
|
||||
my.zip
|
||||
# confirm the zip was added to git media
|
||||
$ git media ls-files
|
||||
my.zip
|
||||
```
|
||||
|
||||
When you can see files being added to the upload queue, you can commit like
|
||||
When you can see files being added to git media, you can commit like
|
||||
normal. After committing, `git show` will show the file's meta data:
|
||||
|
||||
$ git show
|
||||
@ -117,7 +116,7 @@ normal. After committing, `git show` will show the file's meta data:
|
||||
+84ff327f80500d3266bd830891ede1e4fd18b9169936a066573f9b230597a696
|
||||
\ No newline at end of file
|
||||
|
||||
Now, when you run `git push`, all queued files to will be synced to the
|
||||
Now, when you run `git push`, added media files will be synced to the
|
||||
Git Media endpoint.
|
||||
|
||||
$ git push origin master
|
||||
|
@ -16,6 +16,6 @@ Git attributes.
|
||||
|
||||
## SEE ALSO
|
||||
|
||||
git-media-init(1), git-media-push(1), git-media-queues(1), gitattributes(5).
|
||||
git-media-init(1), git-media-push(1), gitattributes(5).
|
||||
|
||||
Part of the git-media(1) suite.
|
||||
|
@ -1,18 +0,0 @@
|
||||
git-media-queues(1) - View queued content to be pushed.
|
||||
=======================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`git media queues`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
The "queues" command displays this list of objects waiting to be pushed to the
|
||||
remote Git Media endpoint. These objects are usually added by
|
||||
git-media-clean(1).
|
||||
|
||||
## SEE ALSO
|
||||
|
||||
git-media-clean(1).
|
||||
|
||||
Part of the git-media(1) suite.
|
@ -30,8 +30,6 @@ commands and low level ("plumbing") commands.
|
||||
View and modify Git Media paths in Git attributes.
|
||||
* git-media-push(1):
|
||||
Push queued large files to the Git Media endpoint.
|
||||
* git-media-queues(1):
|
||||
View queued content to be pushed.
|
||||
|
||||
### Low level commands (plumbing)
|
||||
|
||||
|
@ -91,7 +91,6 @@ func init() {
|
||||
LocalLogDir = filepath.Join(LocalMediaDir, "logs")
|
||||
LocalLinkDir = filepath.Join(LocalMediaDir, "objects")
|
||||
TempDir = filepath.Join(LocalMediaDir, "tmp")
|
||||
queueDir = setupQueueDir()
|
||||
|
||||
if err := os.MkdirAll(TempDir, 0744); err != nil {
|
||||
panic(fmt.Errorf("Error trying to create temp directory in '%s': %s", TempDir, err))
|
||||
|
@ -1,60 +0,0 @@
|
||||
package gitmedia
|
||||
|
||||
import (
|
||||
"github.com/github/git-media/queuedir"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func QueueUpload(sha, filename string) error {
|
||||
fileBody := sha
|
||||
|
||||
if filename != "" {
|
||||
fileBody += ":" + filename
|
||||
}
|
||||
|
||||
q, err := UploadQueue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = q.AddString(fileBody)
|
||||
return err
|
||||
}
|
||||
|
||||
func WalkQueues(cb func(name string, queue *queuedir.Queue) error) error {
|
||||
var err error
|
||||
for name, queuefunc := range queues {
|
||||
q, err := queuefunc()
|
||||
if err == nil {
|
||||
err = cb(name, q)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func UploadQueue() (*queuedir.Queue, error) {
|
||||
if uploadQueue == nil {
|
||||
q, err := queueDir.Queue("upload")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
uploadQueue = q
|
||||
}
|
||||
|
||||
return uploadQueue, nil
|
||||
}
|
||||
|
||||
func setupQueueDir() *queuedir.QueueDir {
|
||||
return queuedir.New(filepath.Join(LocalMediaDir, "queue"))
|
||||
}
|
||||
|
||||
var (
|
||||
queues = map[string]func() (*queuedir.Queue, error){
|
||||
"upload": UploadQueue,
|
||||
}
|
||||
queueDir *queuedir.QueueDir
|
||||
uploadQueue *queuedir.Queue
|
||||
)
|
@ -1,124 +0,0 @@
|
||||
// Package queue implements a simple file system queue. Jobs are stored as
|
||||
// files in a directory. Loosely implements something like maildir, without
|
||||
// any specific code for dealing with email.
|
||||
package queuedir
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/streadway/simpleuuid"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
type QueueDir struct {
|
||||
Path string
|
||||
}
|
||||
|
||||
func New(path string) *QueueDir {
|
||||
return &QueueDir{Path: path}
|
||||
}
|
||||
|
||||
func (q *QueueDir) Queue(name string) (*Queue, error) {
|
||||
qu := &Queue{name, filepath.Join(q.Path, name), q}
|
||||
err := os.MkdirAll(qu.Path, 0777)
|
||||
return qu, err
|
||||
}
|
||||
|
||||
type Queue struct {
|
||||
Name string
|
||||
Path string
|
||||
Dir *QueueDir
|
||||
}
|
||||
|
||||
type WalkFunc func(id string, body []byte) error
|
||||
|
||||
func (q *Queue) Add(reader io.Reader) (string, error) {
|
||||
uuid, err := simpleuuid.NewTime(time.Now())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
id := uuid.String()
|
||||
file, err := os.Create(q.FullPath(id))
|
||||
if err == nil {
|
||||
defer file.Close()
|
||||
_, err = io.Copy(file, reader)
|
||||
}
|
||||
return id, err
|
||||
}
|
||||
|
||||
func (q *Queue) Count() (int, error) {
|
||||
total := 0
|
||||
err := filepath.Walk(q.Path, func(path string, info os.FileInfo, err error) error {
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
total += 1
|
||||
return nil
|
||||
})
|
||||
|
||||
return total, err
|
||||
}
|
||||
|
||||
func (q *Queue) Walk(cb WalkFunc) error {
|
||||
return filepath.Walk(q.Path, func(path string, info os.FileInfo, err error) error {
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return cb(filepath.Base(path), body)
|
||||
})
|
||||
}
|
||||
|
||||
func (q *Queue) AddString(body string) (string, error) {
|
||||
return q.Add(bytes.NewBufferString(body))
|
||||
}
|
||||
|
||||
func (q *Queue) AddBytes(body []byte) (string, error) {
|
||||
return q.Add(bytes.NewBuffer(body))
|
||||
}
|
||||
|
||||
func (q *Queue) Move(newqueue *Queue, id string) error {
|
||||
return os.Rename(q.FullPath(id), newqueue.FullPath(id))
|
||||
}
|
||||
|
||||
func (q *Queue) Del(id string) error {
|
||||
full := q.FullPath(id)
|
||||
stat, err := os.Stat(full)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if stat.IsDir() {
|
||||
return fmt.Errorf("%s in %s is a directory", id, q.Path)
|
||||
}
|
||||
|
||||
return os.Remove(full)
|
||||
}
|
||||
|
||||
func (q *Queue) FullPath(id string) string {
|
||||
return filepath.Join(q.Path, id)
|
||||
}
|
@ -1,153 +0,0 @@
|
||||
package queuedir
|
||||
|
||||
import (
|
||||
"github.com/bmizerany/assert"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAdd(t *testing.T) {
|
||||
q := Setup(t)
|
||||
defer q.Teardown()
|
||||
|
||||
id, err := q.Queue.AddString("BOOM")
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot add to queue: %s", err)
|
||||
}
|
||||
|
||||
assertExist(t, q.Queue, id)
|
||||
file, err := os.Open(filepath.Join(q.Queue.Path, id))
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot open file: %s", err)
|
||||
}
|
||||
|
||||
by, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot read file: %s", err)
|
||||
}
|
||||
|
||||
assert.Equal(t, "BOOM", string(by))
|
||||
}
|
||||
|
||||
func TestWalk(t *testing.T) {
|
||||
q := Setup(t)
|
||||
defer q.Teardown()
|
||||
|
||||
id1, err := q.Queue.AddString("BOOM0")
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot add to queue: %s", err)
|
||||
}
|
||||
|
||||
id2, err := q.Queue.AddString("BOOM1")
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot add to queue: %s", err)
|
||||
}
|
||||
|
||||
seen := make(map[string]bool)
|
||||
|
||||
q.Queue.Walk(func(id string, body []byte) error {
|
||||
if err != nil {
|
||||
t.Errorf("Error reading queue data for %s: %s", id, err)
|
||||
}
|
||||
|
||||
seen[id] = true
|
||||
if id == id1 {
|
||||
assert.Equal(t, id1, id)
|
||||
} else if id == id2 {
|
||||
assert.Equal(t, id2, id)
|
||||
} else {
|
||||
t.Errorf("Weird ID: %s", id)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
assert.Equal(t, 2, len(seen))
|
||||
}
|
||||
|
||||
func TestMove(t *testing.T) {
|
||||
q := Setup(t)
|
||||
defer q.Teardown()
|
||||
|
||||
id, err := q.Queue.AddString("BOOM")
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot add to queue: %s", err)
|
||||
}
|
||||
|
||||
assertExist(t, q.Queue, id)
|
||||
|
||||
queue2, err := q.Dir.Queue("test2")
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot create %s queue: %s", queue2.Name, err)
|
||||
}
|
||||
|
||||
err = q.Queue.Move(queue2, id)
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot move from queue %s to %s: %s", q.Queue.Name, queue2.Name, err)
|
||||
}
|
||||
|
||||
assertNotExist(t, q.Queue, id)
|
||||
assertExist(t, queue2, id)
|
||||
}
|
||||
|
||||
func TestDel(t *testing.T) {
|
||||
q := Setup(t)
|
||||
defer q.Teardown()
|
||||
|
||||
id, err := q.Queue.AddString("BOOM")
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot add to queue: %s", err)
|
||||
}
|
||||
|
||||
assertExist(t, q.Queue, id)
|
||||
|
||||
err = q.Queue.Del(id)
|
||||
if err != nil {
|
||||
t.Fatalf("Error deleting from queue: %s", err)
|
||||
}
|
||||
assertNotExist(t, q.Queue, id)
|
||||
}
|
||||
|
||||
type QueueTest struct {
|
||||
Dir *QueueDir
|
||||
Queue *Queue
|
||||
}
|
||||
|
||||
func Setup(t *testing.T) *QueueTest {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot get current working dir: %s", err)
|
||||
}
|
||||
|
||||
qdir := New(filepath.Join(wd, "test_queuedir"))
|
||||
|
||||
q, err := qdir.Queue("test")
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot create test queue: %s", err)
|
||||
}
|
||||
|
||||
return &QueueTest{qdir, q}
|
||||
}
|
||||
|
||||
func (t *QueueTest) Teardown() {
|
||||
os.RemoveAll(t.Dir.Path)
|
||||
}
|
||||
|
||||
func assertExist(t *testing.T, q *Queue, id string) {
|
||||
if !fileExists(q, id) {
|
||||
t.Fatalf("%s does not exist in queue %s", id, q.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func assertNotExist(t *testing.T, q *Queue, id string) {
|
||||
if fileExists(q, id) {
|
||||
t.Fatalf("%s exists in queue %s", id, q.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func fileExists(q *Queue, id string) bool {
|
||||
_, err := os.Stat(q.FullPath(id))
|
||||
return err == nil
|
||||
}
|
Loading…
Reference in New Issue
Block a user