<commit_before>/*
Copyright 2014 Gavin Bong.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
either express or implied. See the License for the specific
language governing permissions and limitations under the
License.
*/

package redblacktree

import (
    _ "fmt"
    "reflect"
    "testing"
)

var funcs map[string]reflect.Method

func init() {
    var found bool
    var put, rotateLeft, rotateRight reflect.Method

    t := reflect.TypeOf(NewTree())
    put, found = t.MethodByName("Put")
    if !found {
        panic("No method `Put` in Tree")
    }
    rotateLeft, found = t.MethodByName("RotateLeft")
    if !found {
        panic("No method `RotateLeft` in Tree")
    }
    rotateRight, found = t.MethodByName("RotateRight")
    if !found {
        panic("No method `RotateRight` in Tree")
    }

    funcs = map[string]reflect.Method{
        "rotateRight": rotateRight,
        "rotateLeft": rotateLeft,
        "put": put,
    }

    TraceOff()
    //TraceOn()
}

func True(b bool, t *testing.T) {
	if !b {
		t.Errorf("Expected [ %t ] got [ %t ]", true, b)
	}
}

func False(b bool, t *testing.T) {
	if b {
		t.Errorf("Expected [ %t ] got [ %t ]", false, b)
	}
}

func assertDirection(expected Direction, actual Direction, t *testing.T) {
    if actual != expected {
		t.Errorf("Expected (%s) got (%s)", expected, actual)
	}
}

func assertNodeKey(n *Node, expected int, t *testing.T) {
    if n.value != expected {
        t.Errorf("Expected (%#v) got (%#v)", expected, n.value)
    }
}

// using the inorder walk of the tree for equality
func assertEqualTree(tr *Tree, t *testing.T, expected string) {
    visitor := &InorderVisitor{}
    tr.Walk(visitor)
    if visitor.String() != expected {
        t.Errorf("Expected [ %s ] got [ %s ]", expected, visitor)
    }
}

// Value.IsNil returns true if v is a nil value. It panics if
// v's Kind is not Chan, Func, Interface, Map, Ptr, or Slice.
func nillable(k reflect.Kind) bool {
    switch k {
    case reflect.Chan: fallthrough
    case reflect.Func: fallthrough
    case reflect.Interface: fallthrough
    case reflect.Map: fallthrough
    case reflect.Ptr: fallthrough
    case reflect.Slice: return true
    default: return false
    }
}

// asserts that @param `a` is nil
func Nil(a interface{}, t *testing.T) {
    if a == nil {
        return
    }
    value := reflect.ValueOf(a)
    if nillable(value.Kind()) {
        if !value.IsNil() {
			t.Errorf("%#v is not nil", a)
		}
    } else {
        t.Errorf("%#v is not nil", a)
	}
}

// asserts that @param `a` is NOT nil
func NotNil(a interface{}, t *testing.T) {
    if a == nil {
        t.Errorf("Expected NOT nil")
        return
    }
    value := reflect.ValueOf(a)
    if nillable(value.Kind()) {
        if value.IsNil() {
			t.Errorf("%#v is nil but we expected it to be NOT nil", a)
		}
    }
}

type KV struct {
    key int
    arg string
}

func ToArgs(a ...interface{}) []reflect.Value {
    in := make([]reflect.Value, len(a))
    it := make([]struct{}, len(a))
    for i, _ := range it {
        in[i] = reflect.ValueOf(a[i])
    }
    return in
}

var fixtureSmall = []struct {
    ops string
    kv  KV
    expected string
}{
    {"put", KV{7, "payload7"}, "(.7.)"},
    {"put", KV{3, "payload3"}, "((.3.)7.)"},
    {"put", KV{8, "payload8"}, "((.3.)7(.8.))"},
}

func TestRedBlackSmall(t *testing.T) {
    t1 := NewTree()
    if t1.root != nil {
        t.Errorf("root starts out as nil but got (%t)", t1.root != nil)
    }
    assertEqualTree(t1, t, ".")

    t2 := NewTree()
    for _, tt := range fixtureSmall {
        method := funcs[tt.ops]
        in := ToArgs(t2, tt.kv.key, tt.kv.arg)
        method.Func.Call(in) // @TODO returns []reflect.Value ?
        assertEqualTree(t2, t, tt.expected)
    }
}

var fixtureSimpleRightRotation = []struct {
    ops string
    kv  KV
    expected string
}{
    {"put", KV{7, "payload7"}, "(.7.)"},
    {"put", KV{3, "payload3"}, "((.3.)7.)"},
    {"put", KV{1, "payload1"}, "((.1.)3(.7.))"},
}

func TestRedBlackSimpleRightRotation(t *testing.T) {
    tr := NewTree()
    for _, tt := range fixtureSimpleRightRotation {
        method := funcs[tt.ops]
        method.Func.Call(ToArgs(tr, tt.kv.key, tt.kv.arg))
        assertEqualTree(tr, t, tt.expected)
    }
}

var fixtureCase1 = []struct {
    ops string
    kv  KV
    expected string
}{
    {"put", KV{7, "payload7"}, "(.7.)"},
    {"put", KV{8, "payload8"}, "(.7(.8.))"},
    {"put", KV{9, "payload9"}, "((.7.)8(.9.))"},
    {"put", KV{11, "payload11"}, "((.7.)8(.9(.11.)))"},
    {"put", KV{10, "payload10"}, "((.7.)8((.9.)10(.11.)))"},
}

func TestRedBlackCase1(t *testing.T) {
    tr := NewTree()
    for _, tt := range fixtureCase1 {
        method := funcs[tt.ops]
        method.Func.Call(ToArgs(tr, tt.kv.key, tt.kv.arg))
        assertEqualTree(tr, t, tt.expected)
    }
}

var fixtureRotationLeft = []struct {
    ops string
    kv  KV
    expected string
}{
    {"put", KV{7, "payload7"}, "(.7.)"},
    {"rotateLeft", KV{}, "(.7.)"},
    {"put", KV{8, "payload8"}, "(.7(.8.))"},
    {"put", KV{9, "payload9"}, "((.7.)8(.9.))"},
    {"put", KV{11, "payload11"}, "((.7.)8(.9(.11.)))"},
    {"rotateLeft", KV{}, "(((.7.)8.)9(.11.))"},
    {"rotateLeft", KV{}, "((((.7.)8.)9.)11.)"},
    {"rotateLeft", KV{}, "((((.7.)8.)9.)11.)"},
}

func TestRedBlackLeft1(t *testing.T) {
    t1 := NewTree()
    assertEqualTree(t1, t, ".")
    t1.RotateLeft(t1.root)
    assertEqualTree(t1, t, ".")

    for _, tt := range fixtureRotationLeft {
        method := funcs[tt.ops]
        switch {
        case tt.ops == "put":
            method.Func.Call(ToArgs(t1, tt.kv.key, tt.kv.arg))

        case tt.ops == "rotateLeft":
            method.Func.Call(ToArgs(t1, t1.root))
        }
        assertEqualTree(t1, t, tt.expected)
    }
}

var fixtureRotationRight = []struct {
    ops string
    kv  KV
    expected string
}{
    {"put", KV{7, "payload7"}, "(.7.)"},
    {"rotateRight", KV{}, "(.7.)"},
    {"put", KV{3, "payload3"}, "((.3.)7.)"},
    {"put", KV{2, "payload2"}, "((.2.)3(.7.))"},
    {"put", KV{1, "payload1"}, "(((.1.)2.)3(.7.))"},
    {"rotateRight", KV{}, "((.1.)2(.3(.7.)))"},
    {"rotateRight", KV{}, "(.1(.2(.3(.7.))))"},
    {"rotateRight", KV{}, "(.1(.2(.3(.7.))))"},
}

func TestRedBlackRight(t *testing.T) {
    t1 := NewTree()
    assertEqualTree(t1, t, ".")
    t1.RotateRight(t1.root)
    assertEqualTree(t1, t, ".")

    for _, tt := range fixtureRotationRight {
        method := funcs[tt.ops]
        switch {
        case tt.ops == "put":
            method.Func.Call(ToArgs(t1, tt.kv.key, tt.kv.arg))

        case tt.ops == "rotateRight":
            method.Func.Call(ToArgs(t1, t1.root))
        }
        assertEqualTree(t1, t, tt.expected)
    }
}

func TestRedBlackParentLookup(t *testing.T) {
    tr := NewTree()

    // Lookup for something in an empty tree
    found, parent, dir := tr.GetParent(5)
    False(found, t)
    Nil(parent, t)
    assertDirection(NODIR, dir, t)

    key7 := 7
    tr.Put(key7, "payload7")
    // Lookup the root node
    found, parent, dir = tr.GetParent(key7)
    True(found, t)
    Nil(parent, t)
    assertDirection(NODIR, dir, t)

    key3, key11 := 3, 11
    tr.Put(key3, "payload3")
    tr.Put(key11, "payload11")

    found, parent, dir = tr.GetParent(key3)
    True(found, t)
    NotNil(parent, t)
    if parent.value != key7 {
		t.Errorf("Expected root node 7")
    }
    assertDirection(LEFT, dir, t)

    found, parent, dir = tr.GetParent(key11)
    True(found, t)
    NotNil(parent, t)
    if parent.value != key7 {
		t.Errorf("Expected root node 7")
    }
    assertDirection(RIGHT, dir, t)
}

var treeData = []struct {
    ops string
    kv  KV
}{
    {"put", KV{7, "payload7"}},
    {"put", KV{3, "payload3"}},
    {"put", KV{18, "payload18"}},
    {"put", KV{10, "payload10"}},
    {"put", KV{8, "payload8"}},
    {"put", KV{11, "payload11"}},
    {"put", KV{22, "payload22"}},
    {"put", KV{26, "payload26"}},
    {"put", KV{30, "payload30"}},
    {"put", KV{45, "payload45"}},
    {"put", KV{35, "payload35"}},
    {"put", KV{90, "payload90"}},
    {"put", KV{85, "payload85"}},
    {"put", KV{83, "payload83"}},
    {"put", KV{100, "payload100"}},
}

func TestRedBlackNodeLookup(t *testing.T) {
    t1 := NewTree()
    for _, tt := range treeData {
        method := funcs[tt.ops]
        switch {
        case tt.ops == "put":
            method.Func.Call(ToArgs(t1, tt.kv.key, tt.kv.arg))
        }
    }

    key10 := 10
    assertEqualTree(t1, t, "(((.3.)7(.8.))10(((.11.)18(.22.))26((.30.)35((.45(.83.))85(.90(.100.))))))")
    assertNodeKey(t1.root, key10, t)

    // search for the root
    {
        ok, node10 := t1.Get(key10)
        True(ok, t)
        NotNil(node10, t)
        assertNodeKey(node10, key10, t)
    }

    // search for non-existent node
    {
        ok, node6 := t1.Get(6)
        False(ok, t)
        Nil(node6, t)
    }

    // search for nodes that exist
    {
        key3, key100 := 3, 100

        ok, node3 := t1.Get(key3)
        True(ok, t)
        NotNil(node3, t)
        assertNodeKey(node3, key3, t)

        ok, node100 := t1.Get(key100)
        True(ok, t)
        NotNil(node100, t)
        assertNodeKey(node100, key100, t)
    }
}

// Removed prefix `Ignore` to test it.
func IgnoreTestLeftRotateProperly(t *testing.T) {
    t1 := NewTree()
    for i, tt := range treeData {
        if i == 9 {
            break
        }
        method := funcs[tt.ops]
        switch {
        case tt.ops == "put":
            method.Func.Call(ToArgs(t1, tt.kv.key, tt.kv.arg))
        }
    }

    key18 := 18
    ok, node18 := t1.Get(key18)
    True(ok, t)
    NotNil(node18, t)
    assertNodeKey(node18, key18, t)

    /*
      (n) = black
             (10)
            /    \
           7     18
          / \   /  \
        (3) (8)(11)(26)
                    / \
                   22  30
    */
    assertEqualTree(t1, t, "(((.3.)7(.8.))10((.11.)18((.22.)26(.30.))))")

    t1.RotateLeft(node18)
    assertEqualTree(t1, t, "(((.3.)7(.8.))10(((.11.)18(.22.))26(.30.)))")
}
<commit_msg>Added testcases for extremes<commit_after>/*
Copyright 2014 Gavin Bong.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
either express or implied. See the License for the specific
language governing permissions and limitations under the
License.
*/

package redblacktree

import (
    _ "fmt"
    "reflect"
    "sort"
    "testing"
)

var funcs map[string]reflect.Method

func init() {
    var found bool
    var put, rotateLeft, rotateRight reflect.Method

    t := reflect.TypeOf(NewTree())
    put, found = t.MethodByName("Put")
    if !found {
        panic("No method `Put` in Tree")
    }
    rotateLeft, found = t.MethodByName("RotateLeft")
    if !found {
        panic("No method `RotateLeft` in Tree")
    }
    rotateRight, found = t.MethodByName("RotateRight")
    if !found {
        panic("No method `RotateRight` in Tree")
    }

    funcs = map[string]reflect.Method{
        "rotateRight": rotateRight,
        "rotateLeft": rotateLeft,
        "put": put,
    }

    TraceOff()
    //TraceOn()
}

func True(b bool, t *testing.T) {
	if !b {
		t.Errorf("Expected [ %t ] got [ %t ]", true, b)
	}
}

func False(b bool, t *testing.T) {
	if b {
		t.Errorf("Expected [ %t ] got [ %t ]", false, b)
	}
}

func assertDirection(expected Direction, actual Direction, t *testing.T) {
    if actual != expected {
		t.Errorf("Expected (%s) got (%s)", expected, actual)
	}
}

func assertNodeKey(n *Node, expected int, t *testing.T) {
    if n.value != expected {
        t.Errorf("Expected (%#v) got (%#v)", expected, n.value)
    }
}

// using the inorder walk of the tree for equality
func assertEqualTree(tr *Tree, t *testing.T, expected string) {
    visitor := &InorderVisitor{}
    tr.Walk(visitor)
    if visitor.String() != expected {
        t.Errorf("Expected [ %s ] got [ %s ]", expected, visitor)
    }
}

// Value.IsNil returns true if v is a nil value. It panics if
// v's Kind is not Chan, Func, Interface, Map, Ptr, or Slice.
func nillable(k reflect.Kind) bool {
    switch k {
    case reflect.Chan: fallthrough
    case reflect.Func: fallthrough
    case reflect.Interface: fallthrough
    case reflect.Map: fallthrough
    case reflect.Ptr: fallthrough
    case reflect.Slice: return true
    default: return false
    }
}

// asserts that @param `a` is nil
func Nil(a interface{}, t *testing.T) {
    if a == nil {
        return
    }
    value := reflect.ValueOf(a)
    if nillable(value.Kind()) {
        if !value.IsNil() {
			t.Errorf("%#v is not nil", a)
		}
    } else {
        t.Errorf("%#v is not nil", a)
	}
}

// asserts that @param `a` is NOT nil
func NotNil(a interface{}, t *testing.T) {
    if a == nil {
        t.Errorf("Expected NOT nil")
        return
    }
    value := reflect.ValueOf(a)
    if nillable(value.Kind()) {
        if value.IsNil() {
			t.Errorf("%#v is nil but we expected it to be NOT nil", a)
		}
    }
}

type KV struct {
    key int
    arg string
}

type Operation struct {
    ops string
    kv  KV
}

func ToArgs(a ...interface{}) []reflect.Value {
    in := make([]reflect.Value, len(a))
    it := make([]struct{}, len(a))
    for i, _ := range it {
        in[i] = reflect.ValueOf(a[i])
    }
    return in
}

var fixtureSmall = []struct {
    ops string
    kv  KV
    expected string
}{
    {"put", KV{7, "payload7"}, "(.7.)"},
    {"put", KV{3, "payload3"}, "((.3.)7.)"},
    {"put", KV{8, "payload8"}, "((.3.)7(.8.))"},
}

func TestRedBlackSmall(t *testing.T) {
    t1 := NewTree()
    if t1.root != nil {
        t.Errorf("root starts out as nil but got (%t)", t1.root != nil)
    }
    assertEqualTree(t1, t, ".")

    t2 := NewTree()
    for _, tt := range fixtureSmall {
        method := funcs[tt.ops]
        in := ToArgs(t2, tt.kv.key, tt.kv.arg)
        method.Func.Call(in) // @TODO returns []reflect.Value ?
        assertEqualTree(t2, t, tt.expected)
    }
}

var fixtureSimpleRightRotation = []struct {
    ops string
    kv  KV
    expected string
}{
    {"put", KV{7, "payload7"}, "(.7.)"},
    {"put", KV{3, "payload3"}, "((.3.)7.)"},
    {"put", KV{1, "payload1"}, "((.1.)3(.7.))"},
}

func TestRedBlackSimpleRightRotation(t *testing.T) {
    tr := NewTree()
    for _, tt := range fixtureSimpleRightRotation {
        method := funcs[tt.ops]
        method.Func.Call(ToArgs(tr, tt.kv.key, tt.kv.arg))
        assertEqualTree(tr, t, tt.expected)
    }
}

var fixtureCase1 = []struct {
    ops string
    kv  KV
    expected string
}{
    {"put", KV{7, "payload7"}, "(.7.)"},
    {"put", KV{8, "payload8"}, "(.7(.8.))"},
    {"put", KV{9, "payload9"}, "((.7.)8(.9.))"},
    {"put", KV{11, "payload11"}, "((.7.)8(.9(.11.)))"},
    {"put", KV{10, "payload10"}, "((.7.)8((.9.)10(.11.)))"},
}

func TestRedBlackCase1(t *testing.T) {
    tr := NewTree()
    for _, tt := range fixtureCase1 {
        method := funcs[tt.ops]
        method.Func.Call(ToArgs(tr, tt.kv.key, tt.kv.arg))
        assertEqualTree(tr, t, tt.expected)
    }
}

var fixtureRotationLeft = []struct {
    ops string
    kv  KV
    expected string
}{
    {"put", KV{7, "payload7"}, "(.7.)"},
    {"rotateLeft", KV{}, "(.7.)"},
    {"put", KV{8, "payload8"}, "(.7(.8.))"},
    {"put", KV{9, "payload9"}, "((.7.)8(.9.))"},
    {"put", KV{11, "payload11"}, "((.7.)8(.9(.11.)))"},
    {"rotateLeft", KV{}, "(((.7.)8.)9(.11.))"},
    {"rotateLeft", KV{}, "((((.7.)8.)9.)11.)"},
    {"rotateLeft", KV{}, "((((.7.)8.)9.)11.)"},
}

func TestRedBlackLeft1(t *testing.T) {
    t1 := NewTree()
    assertEqualTree(t1, t, ".")
    t1.RotateLeft(t1.root)
    assertEqualTree(t1, t, ".")

    for _, tt := range fixtureRotationLeft {
        method := funcs[tt.ops]
        switch {
        case tt.ops == "put":
            method.Func.Call(ToArgs(t1, tt.kv.key, tt.kv.arg))

        case tt.ops == "rotateLeft":
            method.Func.Call(ToArgs(t1, t1.root))
        }
        assertEqualTree(t1, t, tt.expected)
    }
}

var fixtureRotationRight = []struct {
    ops string
    kv  KV
    expected string
}{
    {"put", KV{7, "payload7"}, "(.7.)"},
    {"rotateRight", KV{}, "(.7.)"},
    {"put", KV{3, "payload3"}, "((.3.)7.)"},
    {"put", KV{2, "payload2"}, "((.2.)3(.7.))"},
    {"put", KV{1, "payload1"}, "(((.1.)2.)3(.7.))"},
    {"rotateRight", KV{}, "((.1.)2(.3(.7.)))"},
    {"rotateRight", KV{}, "(.1(.2(.3(.7.))))"},
    {"rotateRight", KV{}, "(.1(.2(.3(.7.))))"},
}

func TestRedBlackRight(t *testing.T) {
    t1 := NewTree()
    assertEqualTree(t1, t, ".")
    t1.RotateRight(t1.root)
    assertEqualTree(t1, t, ".")

    for _, tt := range fixtureRotationRight {
        method := funcs[tt.ops]
        switch {
        case tt.ops == "put":
            method.Func.Call(ToArgs(t1, tt.kv.key, tt.kv.arg))

        case tt.ops == "rotateRight":
            method.Func.Call(ToArgs(t1, t1.root))
        }
        assertEqualTree(t1, t, tt.expected)
    }
}

func TestRedBlackParentLookup(t *testing.T) {
    tr := NewTree()

    // Lookup for something in an empty tree
    found, parent, dir := tr.GetParent(5)
    False(found, t)
    Nil(parent, t)
    assertDirection(NODIR, dir, t)

    key7 := 7
    tr.Put(key7, "payload7")
    // Lookup the root node
    found, parent, dir = tr.GetParent(key7)
    True(found, t)
    Nil(parent, t)
    assertDirection(NODIR, dir, t)

    key3, key11 := 3, 11
    tr.Put(key3, "payload3")
    tr.Put(key11, "payload11")

    found, parent, dir = tr.GetParent(key3)
    True(found, t)
    NotNil(parent, t)
    if parent.value != key7 {
		t.Errorf("Expected root node 7")
    }
    assertDirection(LEFT, dir, t)

    found, parent, dir = tr.GetParent(key11)
    True(found, t)
    NotNil(parent, t)
    if parent.value != key7 {
		t.Errorf("Expected root node 7")
    }
    assertDirection(RIGHT, dir, t)
}

var treeData = []Operation {
    {"put", KV{7, "payload7"}},
    {"put", KV{3, "payload3"}},
    {"put", KV{18, "payload18"}},
    {"put", KV{10, "payload10"}},
    {"put", KV{8, "payload8"}},
    {"put", KV{11, "payload11"}},
    {"put", KV{22, "payload22"}},
    {"put", KV{26, "payload26"}},
    {"put", KV{30, "payload30"}},
    {"put", KV{45, "payload45"}},
    {"put", KV{35, "payload35"}},
    {"put", KV{90, "payload90"}},
    {"put", KV{85, "payload85"}},
    {"put", KV{83, "payload83"}},
    {"put", KV{100, "payload100"}},
}

func TestRedBlackNodeLookup(t *testing.T) {
    t1 := NewTree()
    for _, tt := range treeData {
        method := funcs[tt.ops]
        switch {
        case tt.ops == "put":
            method.Func.Call(ToArgs(t1, tt.kv.key, tt.kv.arg))
        }
    }

    key10 := 10
    assertEqualTree(t1, t, "(((.3.)7(.8.))10(((.11.)18(.22.))26((.30.)35((.45(.83.))85(.90(.100.))))))")
    assertNodeKey(t1.root, key10, t)

    // search for the root
    {
        ok, node10 := t1.Get(key10)
        True(ok, t)
        NotNil(node10, t)
        assertNodeKey(node10, key10, t)
    }

    // search for non-existent node
    {
        ok, node6 := t1.Get(6)
        False(ok, t)
        Nil(node6, t)
    }

    // search for nodes that exist
    {
        key3, key100 := 3, 100

        ok, node3 := t1.Get(key3)
        True(ok, t)
        NotNil(node3, t)
        assertNodeKey(node3, key3, t)

        ok, node100 := t1.Get(key100)
        True(ok, t)
        NotNil(node100, t)
        assertNodeKey(node100, key100, t)
    }
}

// TIL: Add prefix `Ignore` to skip
func TestLeftRotateProperly(t *testing.T) {
    t1 := NewTree()
    for i, tt := range treeData {
        if i == 9 {
            break
        }
        method := funcs[tt.ops]
        switch {
        case tt.ops == "put":
            method.Func.Call(ToArgs(t1, tt.kv.key, tt.kv.arg))
        }
    }

    key18 := 18
    ok, node18 := t1.Get(key18)
    True(ok, t)
    NotNil(node18, t)
    assertNodeKey(node18, key18, t)

    /*
      (n) = black
             (10)
            /    \
           7     18
          / \   /  \
        (3) (8)(11)(26)
                    / \
                   22  30
    */
    assertEqualTree(t1, t, "(((.3.)7(.8.))10((.11.)18((.22.)26(.30.))))")

    t1.RotateLeft(node18)
    assertEqualTree(t1, t, "(((.3.)7(.8.))10(((.11.)18(.22.))26(.30.)))")
}

type By func(o1, o2 *Operation) bool

func (b By) Sort(ops []Operation) {
    os := &operationSorter{
        operations: ops,
        by:         b,
    }
    sort.Sort(os)
}

type operationSorter struct{
    operations []Operation
    by         func(o1, o2 *Operation) bool
}

func (k operationSorter) Len() int {
    return len(k.operations)
}

func (k operationSorter) Swap(i, j int) {
    k.operations[i], k.operations[j] = k.operations[j], k.operations[i]
}

func (k operationSorter) Less(i, j int) bool {
    return k.by(&k.operations[i], &k.operations[j])
}

var treeData2 = []Operation{
    {"put", KV{1, "payload1"}},
    {"put", KV{2, "payload2"}},
    {"put", KV{3, "payload3"}},
    {"put", KV{4, "payload4"}},
    {"put", KV{5, "payload5"}},
    {"put", KV{6, "payload6"}},
    {"put", KV{7, "payload7"}},
    {"put", KV{8, "payload8"}},
    {"put", KV{9, "payload9"}},
}

// Two extreme cases:
// 1. keys are in ascending order (sorted)
// 2. keys are in descending order (!sorted)
func TestWorstCases(t *testing.T) {
    increasingKey := func(o1, o2 *Operation) bool {
        return o1.kv.key < o2.kv.key
    }
    decreasingKey := func(o1, o2 *Operation) bool {
        return !increasingKey(o1, o2)
    }

    By(increasingKey).Sort(treeData2)
    t1 := NewTree()
    for _, tt := range treeData2 {
        method := funcs[tt.ops]
        switch {
        case tt.ops == "put":
            method.Func.Call(ToArgs(t1, tt.kv.key, tt.kv.arg))
        }
    }
    assertEqualTree(t1, t, "(((.1.)2(.3.))4((.5.)6((.7.)8(.9.))))")
    assertNodeKey(t1.root, 4, t)

    By(decreasingKey).Sort(treeData2)
    t2 := NewTree()
    for _, tt := range treeData2 {
        method := funcs[tt.ops]
        switch {
        case tt.ops == "put":
            method.Func.Call(ToArgs(t2, tt.kv.key, tt.kv.arg))
        }
    }
    assertEqualTree(t2, t, "((((.1.)2(.3.))4(.5.))6((.7.)8(.9.)))")
    assertNodeKey(t2.root, 6, t)
}
<|endoftext|>


<commit_before>package registry

import (
	"log"

	"github.com/golang/protobuf/protoc-gen-go/descriptor"

	plugin "github.com/golang/protobuf/protoc-gen-go/plugin"
)

const restmapimport = "github.com/doozer-de/restgen/pbmap"

// Messages is a map from the messages name ot the Message
type Messages []*Message

func (ms *Messages) Add(m *Message) {
	for _, c := range *ms {
		if c.String() == m.String() {
			return
		}
	}
	(*ms) = append((*ms), m)
}

func (ms *Messages) Get(key string) (*Message, bool) {
	for _, c := range *ms {
		if c.String() == key {
			return c, true
		}
	}
	return nil, false
}

// The Registry is the root for registering the Types found in the protobuf structures from the compiler
type Registry struct {
	Files    map[string]*File
	FilesI   map[int]*File // To build a path hierarchy to get the source locations
	Service  *Service
	RootFile *File
	Messages Messages
	Package  string
}

func (r *Registry) registerMessageProto(pkg string, d *descriptor.DescriptorProto) {
	var fields Fields

	m := &Message{
		Package:  pkg,
		Type:     d,
		Fields:   fields,
		Registry: r,
	}

	for _, field := range d.GetField() {
		fields = append(fields, NewField(field, m, r))
	}

	m.Fields = fields

	r.Messages.Add(m)
}

func (r *Registry) registerServiceProto(file *File) {
	svcs := file.File.GetService()

	if len(svcs) == 0 {
		return
	}
	svc := svcs[0]
	if r.Service != nil {
		log.Fatal("Only one service in file supported")
	}

	gopkg := *file.File.GetOptions().GoPackage

	s := &Service{
		Package:   file.Package,
		GoPackage: gopkg,
		Name:      svc.GetName(),
		Type:      svc,
		Imports:   []string{},
		Methods:   []*Method{},
		Registry:  r,
		File:      file,
	}

	r.Service = s
	r.RootFile = s.File

	ext, err := r.Service.getServiceMapExtension()

	if err != nil {
		return
	}

	r.Service.BaseURI = ext.BaseUri
	r.Service.TargetPackage = ext.TargetPackage
	r.Service.Version = ext.Version

	for _, method := range svc.GetMethod() {
		r.Service.RegisterMethod(method)
	}
}

// New createsa  new Registry that will read all the information neede from the given CodeGeneratorRequest
func New(r *plugin.CodeGeneratorRequest) *Registry {
	reg := &Registry{
		Files:    make(map[string]*File),
		Messages: []*Message{},
	}

	files := r.GetProtoFile()

	// Register all Messages
	for _, f := range files {
		reg.Files[f.GetName()] = &File{File: f, Name: f.GetName()}
		pkg := f.GetPackage()

		for _, m := range f.GetMessageType() {
			reg.registerMessageProto(pkg, m)
		}
	}

	// The last file is the service file we want to generate code for (the imports come first)
	rootFile := files[len(files)-1]
	reg.RootFile = NewFile(rootFile, reg)
	reg.registerServiceProto(reg.RootFile)

	return reg
}

// QueryStringParam is provides all the information nessesary for the code generation to map on query string parameter to a go field
type QueryStringParam struct {
	Converter string
	Field     string // can also be more deep:
}
<commit_msg>another map removed<commit_after>package registry

import (
	"log"

	"github.com/golang/protobuf/protoc-gen-go/descriptor"

	plugin "github.com/golang/protobuf/protoc-gen-go/plugin"
)

const restmapimport = "github.com/doozer-de/restgen/pbmap"

// Messages is a map from the messages name ot the Message
type Messages []*Message

func (ms *Messages) Add(m *Message) {
	for _, c := range *ms {
		if c.String() == m.String() {
			return
		}
	}
	(*ms) = append((*ms), m)
}

func (ms *Messages) Get(key string) (*Message, bool) {
	for _, c := range *ms {
		if c.String() == key {
			return c, true
		}
	}
	return nil, false
}

type Files []*File

func (fs *Files) Add(f *File) {
	for _, c := range *fs {
		if c.Name == f.Name {
			return
		}
	}
	(*fs) = append((*fs), f)
}

func (fs *Files) Get(key string) (*File, bool) {
	for _, c := range *fs {
		if c.Name == key {
			return c, true
		}
	}
	return nil, false
}

// The Registry is the root for registering the Types found in the protobuf structures from the compiler
type Registry struct {
	Files    Files
	Service  *Service
	RootFile *File
	Messages Messages
	Package  string
}

func (r *Registry) registerMessageProto(pkg string, d *descriptor.DescriptorProto) {
	var fields Fields

	m := &Message{
		Package:  pkg,
		Type:     d,
		Fields:   fields,
		Registry: r,
	}

	for _, field := range d.GetField() {
		fields = append(fields, NewField(field, m, r))
	}

	m.Fields = fields

	r.Messages.Add(m)
}

func (r *Registry) registerServiceProto(file *File) {
	svcs := file.File.GetService()

	if len(svcs) == 0 {
		return
	}
	svc := svcs[0]
	if r.Service != nil {
		log.Fatal("Only one service in file supported")
	}

	gopkg := *file.File.GetOptions().GoPackage

	s := &Service{
		Package:   file.Package,
		GoPackage: gopkg,
		Name:      svc.GetName(),
		Type:      svc,
		Imports:   []string{},
		Methods:   []*Method{},
		Registry:  r,
		File:      file,
	}

	r.Service = s
	r.RootFile = s.File

	ext, err := r.Service.getServiceMapExtension()

	if err != nil {
		return
	}

	r.Service.BaseURI = ext.BaseUri
	r.Service.TargetPackage = ext.TargetPackage
	r.Service.Version = ext.Version

	for _, method := range svc.GetMethod() {
		r.Service.RegisterMethod(method)
	}
}

// New createsa  new Registry that will read all the information neede from the given CodeGeneratorRequest
func New(r *plugin.CodeGeneratorRequest) *Registry {
	reg := &Registry{
		Files:    []*File{},
		Messages: []*Message{},
	}

	files := r.GetProtoFile()

	// Register all Messages
	for _, f := range files {
		reg.Files.Add(&File{File: f, Name: f.GetName()})
		pkg := f.GetPackage()

		for _, m := range f.GetMessageType() {
			reg.registerMessageProto(pkg, m)
		}
	}

	// The last file is the service file we want to generate code for (the imports come first)
	rootFile := files[len(files)-1]
	reg.RootFile = NewFile(rootFile, reg)
	reg.registerServiceProto(reg.RootFile)

	return reg
}

// QueryStringParam is provides all the information nessesary for the code generation to map on query string parameter to a go field
type QueryStringParam struct {
	Converter string
	Field     string // can also be more deep:
}
<|endoftext|>


<commit_before>/*
Copyright 2018 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package typed

import (
	"fmt"

	yaml "gopkg.in/yaml.v2"

	"sigs.k8s.io/structured-merge-diff/schema"
	"sigs.k8s.io/structured-merge-diff/value"
)

// YAMLObject is an object encoded in YAML.
type YAMLObject string

// Parser implements YAMLParser and allows introspecting the schema.
type Parser struct {
	Schema schema.Schema
}

// create builds an unvalidated parser.
func create(schema YAMLObject) (*Parser, error) {
	p := Parser{}
	err := yaml.Unmarshal([]byte(schema), &p.Schema)
	return &p, err
}

func createOrDie(schema YAMLObject) *Parser {
	p, err := create(schema)
	if err != nil {
		panic(fmt.Errorf("failed to create parser: %v", err))
	}
	return p
}

var ssParser = createOrDie(YAMLObject(schema.SchemaSchemaYAML))

// NewParser will build a YAMLParser from a schema. The schema is validated.
func NewParser(schema YAMLObject) (*Parser, error) {
	_, err := ssParser.FromYAML(schema, "schema")
	if err != nil {
		return nil, fmt.Errorf("unable to validate schema: %v", err)
	}
	return create(schema)
}

func (p *Parser) FromYAML(object YAMLObject, typename string) (TypedValue, error) {
	v, err := value.FromYAML([]byte(object))
	if err != nil {
		return TypedValue{}, err
	}
	return AsTyped(v, &p.Schema, typename)
}
<commit_msg>Allow parser to create empty object of a given type<commit_after>/*
Copyright 2018 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package typed

import (
	"fmt"

	yaml "gopkg.in/yaml.v2"
	"sigs.k8s.io/structured-merge-diff/schema"
	"sigs.k8s.io/structured-merge-diff/value"
)

// YAMLObject is an object encoded in YAML.
type YAMLObject string

// Parser implements YAMLParser and allows introspecting the schema.
type Parser struct {
	Schema schema.Schema
}

// create builds an unvalidated parser.
func create(schema YAMLObject) (*Parser, error) {
	p := Parser{}
	err := yaml.Unmarshal([]byte(schema), &p.Schema)
	return &p, err
}

func createOrDie(schema YAMLObject) *Parser {
	p, err := create(schema)
	if err != nil {
		panic(fmt.Errorf("failed to create parser: %v", err))
	}
	return p
}

var ssParser = createOrDie(YAMLObject(schema.SchemaSchemaYAML))

// NewParser will build a YAMLParser from a schema. The schema is validated.
func NewParser(schema YAMLObject) (*Parser, error) {
	_, err := ssParser.FromYAML(schema, "schema")
	if err != nil {
		return nil, fmt.Errorf("unable to validate schema: %v", err)
	}
	return create(schema)
}

// NewEmpty returns a new empty object with the current schema and the
// type "typename".
func (p *Parser) NewEmpty(typename string) (TypedValue, error) {
	return p.FromYAML(YAMLObject("{}"), typename)
}

// FromYAML parses a yaml string into an object with the current schema
// and the type "typename" or an error if validation fails.
func (p *Parser) FromYAML(object YAMLObject, typename string) (TypedValue, error) {
	v, err := value.FromYAML([]byte(object))
	if err != nil {
		return TypedValue{}, err
	}
	return AsTyped(v, &p.Schema, typename)
}
<|endoftext|>


<commit_before>/*
Copyright 2015 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package mungers

import (
	"math"
	"math/rand"

	"k8s.io/kubernetes/pkg/util/sets"
	"k8s.io/test-infra/mungegithub/features"
	"k8s.io/test-infra/mungegithub/github"

	"github.com/golang/glog"
	githubapi "github.com/google/go-github/github"
	"github.com/spf13/cobra"
)

// weightMap is a map of user to a weight for that user.
type weightMap map[string]int64

// A BlunderbussConfig maps a set of file prefixes to a set of owner names (github users)
type BlunderbussConfig struct {
	PrefixMap map[string][]string `json:"prefixMap,omitempty" yaml:"prefixMap,omitempty"`
}

// BlunderbussMunger will assign issues to users based on the config file
// provided by --blunderbuss-config.
type BlunderbussMunger struct {
	config              *BlunderbussConfig
	features            *features.Features
	BlunderbussReassign bool
}

func init() {
	blunderbuss := &BlunderbussMunger{}
	RegisterMungerOrDie(blunderbuss)
}

// Name is the name usable in --pr-mungers
func (b *BlunderbussMunger) Name() string { return "blunderbuss" }

// RequiredFeatures is a slice of 'features' that must be provided
func (b *BlunderbussMunger) RequiredFeatures() []string {
	return []string{features.RepoFeatureName, features.AliasesFeature}
}

// Initialize will initialize the munger
func (b *BlunderbussMunger) Initialize(config *github.Config, features *features.Features) error {
	b.features = features
	return nil
}

// EachLoop is called at the start of every munge loop
func (b *BlunderbussMunger) EachLoop() error { return nil }

// AddFlags will add any request flags to the cobra `cmd`
func (b *BlunderbussMunger) AddFlags(cmd *cobra.Command, config *github.Config) {
	cmd.Flags().BoolVar(&b.BlunderbussReassign, "blunderbuss-reassign", false, "Assign PRs even if they're already assigned; use with -dry-run to judge changes to the assignment algorithm")
}

func chance(val, total int64) float64 {
	return 100.0 * float64(val) / float64(total)
}

func printChance(owners weightMap, total int64) {
	if !glog.V(4) {
		return
	}
	glog.Infof("Owner\tPercent")
	for name, weight := range owners {
		glog.Infof("%s\t%02.2f%%", name, chance(weight, total))
	}
}

func getPotentialOwners(author string, feats *features.Features, files []*githubapi.CommitFile, leafOnly bool) (weightMap, int64) {
	potentialOwners := weightMap{}
	weightSum := int64(0)
	aliases := feats.Aliases
	var fileOwners sets.String
	for _, file := range files {
		if file == nil {
			continue
		}
		fileWeight := int64(1)
		if file.Changes != nil && *file.Changes != 0 {
			fileWeight = int64(*file.Changes)
		}
		// Judge file size on a log scale-- effectively this
		// makes three buckets, we shouldn't have many 10k+
		// line changes.
		fileWeight = int64(math.Log10(float64(fileWeight))) + 1
		if leafOnly {
			fileOwners = feats.Repos.LeafReviewers(*file.Filename)
		} else {
			fileOwners = feats.Repos.Reviewers(*file.Filename)
		}

		if fileOwners.Len() == 0 {
			glog.Warningf("Couldn't find an owner for: %s", *file.Filename)
		}

		if aliases != nil && aliases.IsEnabled {
			fileOwners = aliases.Expand(fileOwners)
		}

		for _, owner := range fileOwners.List() {
			if owner == author {
				continue
			}
			potentialOwners[owner] = potentialOwners[owner] + fileWeight
			weightSum += fileWeight
		}
	}
	return potentialOwners, weightSum
}

// Munge is the workhorse the will actually make updates to the PR
func (b *BlunderbussMunger) Munge(obj *github.MungeObject) {
	if !obj.IsPR() {
		return
	}

	issue := obj.Issue
	if !b.BlunderbussReassign && issue.Assignee != nil {
		glog.V(6).Infof("skipping %v: reassign: %v assignee: %v", *issue.Number, b.BlunderbussReassign, github.DescribeUser(issue.Assignee))
		return
	}

	files, ok := obj.ListFiles()
	if !ok {
		return
	}

	potentialOwners, weightSum := getPotentialOwners(*obj.Issue.User.Login, b.features, files, true)
	if len(potentialOwners) == 0 {
		potentialOwners, weightSum = getPotentialOwners(*obj.Issue.User.Login, b.features, files, false)
		if len(potentialOwners) == 0 {
			glog.Errorf("No OWNERS found for PR %d", *issue.Number)
			return
		}
	}
	printChance(potentialOwners, weightSum)
	if issue.Assignee != nil {
		cur := *issue.Assignee.Login
		c := chance(potentialOwners[cur], weightSum)
		glog.Infof("Current assignee %v has a %02.2f%% chance of having been chosen", cur, c)
	}
	selection := rand.Int63n(weightSum)
	owner := ""
	for o, w := range potentialOwners {
		owner = o
		selection -= w
		if selection <= 0 {
			break
		}
	}
	c := chance(potentialOwners[owner], weightSum)
	glog.Infof("Assigning %v to %v who had a %02.2f%% chance to be assigned (previously assigned to %v)", *issue.Number, owner, c, github.DescribeUser(issue.Assignee))
	obj.AddAssignee(owner)
}
<commit_msg>mungebot: Blunderbuss selects multiple assignees<commit_after>/*
Copyright 2015 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package mungers

import (
	"math"
	"math/rand"

	"k8s.io/kubernetes/pkg/util/sets"
	"k8s.io/test-infra/mungegithub/features"
	"k8s.io/test-infra/mungegithub/github"

	"github.com/golang/glog"
	githubapi "github.com/google/go-github/github"
	"github.com/spf13/cobra"
)

// weightMap is a map of user to a weight for that user.
type weightMap map[string]int64

// A BlunderbussConfig maps a set of file prefixes to a set of owner names (github users)
type BlunderbussConfig struct {
	PrefixMap map[string][]string `json:"prefixMap,omitempty" yaml:"prefixMap,omitempty"`
}

// BlunderbussMunger will assign issues to users based on the config file
// provided by --blunderbuss-config.
type BlunderbussMunger struct {
	config              *BlunderbussConfig
	features            *features.Features
	BlunderbussReassign bool
	NumAssignees        int
}

func init() {
	blunderbuss := &BlunderbussMunger{}
	RegisterMungerOrDie(blunderbuss)
}

// Name is the name usable in --pr-mungers
func (b *BlunderbussMunger) Name() string { return "blunderbuss" }

// RequiredFeatures is a slice of 'features' that must be provided
func (b *BlunderbussMunger) RequiredFeatures() []string {
	return []string{features.RepoFeatureName, features.AliasesFeature}
}

// Initialize will initialize the munger
func (b *BlunderbussMunger) Initialize(config *github.Config, features *features.Features) error {
	b.features = features
	return nil
}

// EachLoop is called at the start of every munge loop
func (b *BlunderbussMunger) EachLoop() error { return nil }

// AddFlags will add any request flags to the cobra `cmd`
func (b *BlunderbussMunger) AddFlags(cmd *cobra.Command, config *github.Config) {
	cmd.Flags().BoolVar(&b.BlunderbussReassign, "blunderbuss-reassign", false, "Assign PRs even if they're already assigned; use with -dry-run to judge changes to the assignment algorithm")
	cmd.Flags().IntVar(&b.NumAssignees, "blunderbuss-number-assignees", 2, "Number of assignees to select for each PR.")
}

func chance(val, total int64) float64 {
	return 100.0 * float64(val) / float64(total)
}

func printChance(owners weightMap, total int64) {
	if !glog.V(4) {
		return
	}
	glog.Infof("Owner\tPercent")
	for name, weight := range owners {
		glog.Infof("%s\t%02.2f%%", name, chance(weight, total))
	}
}

func getPotentialOwners(author string, feats *features.Features, files []*githubapi.CommitFile, leafOnly bool) (weightMap, int64) {
	potentialOwners := weightMap{}
	weightSum := int64(0)
	aliases := feats.Aliases
	var fileOwners sets.String
	for _, file := range files {
		if file == nil {
			continue
		}
		fileWeight := int64(1)
		if file.Changes != nil && *file.Changes != 0 {
			fileWeight = int64(*file.Changes)
		}
		// Judge file size on a log scale-- effectively this
		// makes three buckets, we shouldn't have many 10k+
		// line changes.
		fileWeight = int64(math.Log10(float64(fileWeight))) + 1
		if leafOnly {
			fileOwners = feats.Repos.LeafReviewers(*file.Filename)
		} else {
			fileOwners = feats.Repos.Reviewers(*file.Filename)
		}

		if fileOwners.Len() == 0 {
			glog.Warningf("Couldn't find an owner for: %s", *file.Filename)
		}

		if aliases != nil && aliases.IsEnabled {
			fileOwners = aliases.Expand(fileOwners)
		}

		for _, owner := range fileOwners.List() {
			if owner == author {
				continue
			}
			potentialOwners[owner] = potentialOwners[owner] + fileWeight
			weightSum += fileWeight
		}
	}
	return potentialOwners, weightSum
}

func selectMultipleOwners(potentialOwners weightMap, weightSum int64, count int) []string {
	// Make a copy of the map
	pOwners := weightMap{}
	for k, v := range potentialOwners {
		pOwners[k] = v
	}

	owners := []string{}

	for i := 0; i < count; i++ {
		if len(pOwners) == 0 || weightSum == 0 {
			break
		}
		selection := rand.Int63n(weightSum)
		owner := ""
		for o, w := range pOwners {
			owner = o
			selection -= w
			if selection <= 0 {
				break
			}
		}

		owners = append(owners, owner)
		weightSum -= pOwners[owner]

		// Remove this person from the map.
		delete(pOwners, owner)
	}
	return owners
}

// Munge is the workhorse the will actually make updates to the PR
func (b *BlunderbussMunger) Munge(obj *github.MungeObject) {
	if !obj.IsPR() {
		return
	}

	issue := obj.Issue
	if !b.BlunderbussReassign && issue.Assignee != nil {
		glog.V(6).Infof("skipping %v: reassign: %v assignee: %v", *issue.Number, b.BlunderbussReassign, github.DescribeUser(issue.Assignee))
		return
	}

	files, ok := obj.ListFiles()
	if !ok {
		return
	}

	potentialOwners, weightSum := getPotentialOwners(*obj.Issue.User.Login, b.features, files, true)
	if len(potentialOwners) == 0 {
		potentialOwners, weightSum = getPotentialOwners(*obj.Issue.User.Login, b.features, files, false)
		if len(potentialOwners) == 0 {
			glog.Errorf("No OWNERS found for PR %d", *issue.Number)
			return
		}
	}
	printChance(potentialOwners, weightSum)
	if issue.Assignee != nil {
		cur := *issue.Assignee.Login
		c := chance(potentialOwners[cur], weightSum)
		glog.Infof("Current assignee %v has a %02.2f%% chance of having been chosen", cur, c)
	}

	owners := selectMultipleOwners(potentialOwners, weightSum, b.NumAssignees)

	for _, owner := range owners {
		c := chance(potentialOwners[owner], weightSum)
		glog.Infof("Assigning %v to %v who had a %02.2f%% chance to be assigned", *issue.Number, owner, c)
		obj.AddAssignee(owner)
	}
}
<|endoftext|>


<commit_before>// Copyright 2013 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package collector

import (
	"fmt"
	"github.com/globocom/config"
	"github.com/globocom/tsuru/app"
	"github.com/globocom/tsuru/log"
	"github.com/globocom/tsuru/provision"
	stdlog "log"
	"log/syslog"
	"os"
	"time"
)

func collect(ticker <-chan time.Time) {
	for _ = range ticker {
		units, err := app.Provisioner.CollectStatus()
		if err != nil {
			log.Printf("Failed to collect status within the provisioner: %s.", err)
		}
		update(units)
	}
}

func fatal(err error) {
	fmt.Fprintln(os.Stderr, err)
	log.Fatal(err)
}

func Run(flags map[string]interface{}) {
	logger, err := syslog.NewLogger(syslog.LOG_INFO, stdlog.LstdFlags)
	if err != nil {
		stdlog.Fatal(err)
	}
	log.SetLogger(logger)
	configFile, ok := flags["config"].(string)
	if !ok {
		configFile = "/etc/tsuru/tsuru.conf"
	}
	dry, ok := flags["dry"].(bool)
	if !ok {
		dry = false
	}
	err = config.ReadAndWatchConfigFile(configFile)
	if err != nil {
		fatal(err)
	}
	connString, err := config.GetString("database:url")
	if err != nil {
		fatal(err)
	}
	dbName, err := config.GetString("database:name")
	if err != nil {
		fatal(err)
	}
	fmt.Printf("Using the database %q from the server %q.\n\n", dbName, connString)
	if !dry {
		provisioner, err := config.GetString("provisioner")
		if err != nil {
			fmt.Printf("Warning: %q didn't declare a provisioner, using default provisioner.\n", configFile)
			provisioner = "juju"
		}
		app.Provisioner, err = provision.Get(provisioner)
		if err != nil {
			fatal(err)
		}
		fmt.Printf("Using %q provisioner.\n\n", provisioner)

		ticker := time.Tick(time.Minute)
		fmt.Println("tsuru collector agent started...")
		collect(ticker)
	}
}
<commit_msg>collector: logging one step back<commit_after>// Copyright 2013 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package collector

import (
	"fmt"
	"github.com/globocom/config"
	"github.com/globocom/tsuru/app"
	"github.com/globocom/tsuru/log"
	"github.com/globocom/tsuru/provision"
	stdlog "log"
	"log/syslog"
	"os"
	"time"
)

func collect(ticker <-chan time.Time) {
	for _ = range ticker {
		log.Print("Collecting status from provisioner")
		units, err := app.Provisioner.CollectStatus()
		if err != nil {
			log.Printf("Failed to collect status within the provisioner: %s.", err)
			continue
		}
		update(units)
	}
}

func fatal(err error) {
	fmt.Fprintln(os.Stderr, err)
	log.Fatal(err)
}

func Run(flags map[string]interface{}) {
	logger, err := syslog.NewLogger(syslog.LOG_INFO, stdlog.LstdFlags)
	if err != nil {
		stdlog.Fatal(err)
	}
	log.SetLogger(logger)
	configFile, ok := flags["config"].(string)
	if !ok {
		configFile = "/etc/tsuru/tsuru.conf"
	}
	dry, ok := flags["dry"].(bool)
	if !ok {
		dry = false
	}
	err = config.ReadAndWatchConfigFile(configFile)
	if err != nil {
		fatal(err)
	}
	connString, err := config.GetString("database:url")
	if err != nil {
		fatal(err)
	}
	dbName, err := config.GetString("database:name")
	if err != nil {
		fatal(err)
	}
	fmt.Printf("Using the database %q from the server %q.\n\n", dbName, connString)
	if !dry {
		provisioner, err := config.GetString("provisioner")
		if err != nil {
			fmt.Printf("Warning: %q didn't declare a provisioner, using default provisioner.\n", configFile)
			provisioner = "juju"
		}
		app.Provisioner, err = provision.Get(provisioner)
		if err != nil {
			fatal(err)
		}
		fmt.Printf("Using %q provisioner.\n\n", provisioner)

		ticker := time.Tick(time.Minute)
		fmt.Println("tsuru collector agent started...")
		collect(ticker)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"database/sql"
	"fmt"
	"image"
	_ "image/jpeg"
	"log"
	"mime"
	"os"
	"path/filepath"

	"github.com/agorf/goexif/exif"
	_ "github.com/mattn/go-sqlite3"
)

type Photo struct {
	Aperture      sql.NullFloat64
	Camera        sql.NullString
	ExposureComp  sql.NullInt64
	ExposureTime  sql.NullFloat64
	Flash         sql.NullString
	FocalLength   sql.NullFloat64
	FocalLength35 sql.NullInt64
	Folder        string
	Height        int
	ISO           sql.NullInt64
	Lat           sql.NullFloat64
	Lens          sql.NullString
	Lng           sql.NullFloat64
	Path          string
	Size          int64
	Taken         sql.NullString
	Width         int
}

var (
	db                                                             *sql.DB
	selectSetStmt, selectPhotoStmt, insertSetStmt, insertPhotoStmt *sql.Stmt
)

func decodePhotoExif(photo *Photo, x *exif.Exif) {
	taken, err := x.DateTime()
	if err == nil {
		photo.Taken.String = taken.UTC().Format("2006-01-02 15:04:05")
		photo.Taken.Valid = true
	}

	lat, lng, err := x.LatLong()
	if err == nil {
		photo.Lat.Float64 = lat
		photo.Lat.Valid = true
		photo.Lng.Float64 = lng
		photo.Lng.Valid = true
	}

	orientTag, err := x.Get(exif.Orientation)
	if err == nil {
		switch orient, _ := orientTag.Int(0); orient {
		case 5, 6, 7, 8: // rotated
			photo.Width, photo.Height = photo.Height, photo.Width // swap
		}
	}

	camMakeTag, err := x.Get(exif.Make)
	if err == nil {
		photo.Camera.String, _ = camMakeTag.StringVal()
		photo.Camera.Valid = true
	}

	camModelTag, err := x.Get(exif.Model)
	if err == nil {
		cameraModel, _ := camModelTag.StringVal()

		if photo.Camera.Valid {
			photo.Camera.String = fmt.Sprint(photo.Camera.String, " ", cameraModel)
		} else {
			photo.Camera.String = cameraModel
			photo.Camera.Valid = true
		}
	}

	lensMakeTag, err := x.Get(exif.LensMake)
	if err == nil {
		photo.Lens.String, _ = lensMakeTag.StringVal()
		photo.Lens.Valid = true
	}

	lensModelTag, err := x.Get(exif.LensModel)
	if err == nil {
		lensModel, _ := lensModelTag.StringVal()

		if photo.Lens.Valid {
			photo.Lens.String = fmt.Sprint(photo.Lens.String, " ", lensModel)
		} else {
			photo.Lens.String = lensModel
			photo.Lens.Valid = true
		}
	}

	focalLenTag, err := x.Get(exif.FocalLength)
	if err == nil {
		focalLen, _ := focalLenTag.Rat(0)
		photo.FocalLength.Float64, _ = focalLen.Float64()
		photo.FocalLength.Valid = true
	}

	focalLen35Tag, err := x.Get(exif.FocalLengthIn35mmFilm)
	if err == nil {
		photo.FocalLength35.Int64, _ = focalLen35Tag.Int64(0)
		photo.FocalLength35.Valid = true
	}

	apertureTag, err := x.Get(exif.FNumber)
	if err == nil {
		aperture, _ := apertureTag.Rat(0)
		photo.Aperture.Float64, _ = aperture.Float64()
		photo.Aperture.Valid = true
	}

	expTimeTag, err := x.Get(exif.ExposureTime)
	if err == nil {
		expTime, _ := expTimeTag.Rat(0)
		photo.ExposureTime.Float64, _ = expTime.Float64()
		photo.ExposureTime.Valid = true
	}

	isoTag, err := x.Get(exif.ISOSpeedRatings)
	if err == nil {
		photo.ISO.Int64, _ = isoTag.Int64(0)
		photo.ISO.Valid = true
	}

	expBiasTag, err := x.Get(exif.ExposureBiasValue)
	if err == nil {
		photo.ExposureComp.Int64, _ = expBiasTag.Int64(0)
		photo.ExposureComp.Valid = true
	}

	flash, err := x.Flash()
	if err == nil {
		photo.Flash.String = flash
		photo.Flash.Valid = true
	}
}

func decodePhoto(path string) (*Photo, error) {
	var photo Photo

	photo.Path = path
	photo.Folder = filepath.Base(filepath.Dir(path))

	f, err := os.Open(path)
	if err != nil {
		return &photo, err
	}
	defer f.Close()

	fi, err := f.Stat()
	if err != nil {
		return &photo, err
	}
	photo.Size = fi.Size()

	img, _, err := image.DecodeConfig(f)
	if err != nil {
		return &photo, err
	}
	photo.Width, photo.Height = img.Width, img.Height

	f.Seek(0, 0) // rewind

	x, err := exif.Decode(f)
	if err == nil { // EXIF data exists
		decodePhotoExif(&photo, x)
	}

	return &photo, nil
}

func storePhoto(photo *Photo) error {
	var setId, photoId int64

	row := selectSetStmt.QueryRow(photo.Folder)
	if err := row.Scan(&setId); err == sql.ErrNoRows { // set does not exist
		result, err := insertSetStmt.Exec(photo.Folder) // create it
		if err != nil {
			return err
		}

		setId, err = result.LastInsertId()
		if err != nil {
			return err
		}
	}

	row = selectPhotoStmt.QueryRow(photo.Path)
	if err := row.Scan(&photoId); err == sql.ErrNoRows { // photo does not exist
		result, err := insertPhotoStmt.Exec(photo.Aperture, photo.Camera,
			photo.ExposureComp, photo.ExposureTime, photo.Flash, photo.FocalLength,
			photo.FocalLength35, photo.Height, photo.ISO, photo.Lat, photo.Lens,
			photo.Lng, photo.Path, setId, photo.Size, photo.Taken, photo.Width) // create it
		if err != nil {
			return err
		}

		photoId, err = result.LastInsertId()
		if err != nil {
			return err
		}

		fmt.Fprintf(os.Stderr, "photos id=%d path=%s\n", photoId, photo.Path)
	}

	return nil
}

func walk(path string, info os.FileInfo, err error) error {
	if err != nil { // error walking "path"
		return nil // skip
	}

	if info.IsDir() {
		return nil // skip
	}

	if mime.TypeByExtension(filepath.Ext(path)) != "image/jpeg" { // not JPEG
		return nil // skip
	}

	photo, err := decodePhoto(path)
	if err == nil {
		storePhoto(photo)
	}

	return nil // next
}

func updatePhotoSiblings() error {
	var prevId, prevSetId int

	tx, err := db.Begin()
	if err != nil {
		return err
	}

	updatePrevPhotoStmt, err := tx.Prepare(`
	UPDATE photos SET prev_photo_id = ? WHERE id = ?
	`)
	if err != nil {
		return err
	}
	defer updatePrevPhotoStmt.Close()

	updateNextPhotoStmt, err := tx.Prepare(`
	UPDATE photos SET next_photo_id = ? WHERE id = ?
	`)
	if err != nil {
		return err
	}
	defer updateNextPhotoStmt.Close()

	rows, err := tx.Query(`
	SELECT id, set_id FROM photos ORDER BY set_id, taken_at
	`)
	if err != nil {
		return err
	}
	defer rows.Close()

	for rows.Next() {
		var id, setId int
		rows.Scan(&id, &setId)

		if setId == prevSetId && prevId > 0 {
			updatePrevPhotoStmt.Exec(prevId, id)
			fmt.Fprintf(os.Stderr, "photos id=%d prev_photo_id=%d\n", id, prevId)
			updateNextPhotoStmt.Exec(id, prevId)
			fmt.Fprintf(os.Stderr, "photos id=%d next_photo_id=%d\n", prevId, id)
		}

		prevId = id
		prevSetId = setId
	}

	if err := rows.Err(); err != nil {
		return err
	}

	if err := tx.Commit(); err != nil {
		return err
	}

	return nil
}

func updateSets() error {
	tx, err := db.Begin()
	if err != nil {
		return err
	}

	photosCountStmt, err := tx.Prepare(`
	SELECT COUNT(*) FROM photos WHERE set_id = ?
	`)
	if err != nil {
		return err
	}
	defer photosCountStmt.Close()

	updateSetStmt, err := tx.Prepare(`
	UPDATE sets
	SET photos_count = ?, taken_at = ?, thumb_photo_id = ?
	WHERE id = ?
	`)
	if err != nil {
		return err
	}
	defer updateSetStmt.Close()

	rows, err := tx.Query(`
	SELECT id, set_id, MIN(taken_at) FROM photos GROUP BY set_id
	`)
	if err != nil {
		return err
	}
	defer rows.Close()

	for rows.Next() {
		var id, setId, photosCount int
		var taken sql.NullString

		rows.Scan(&id, &setId, &taken)

		row := photosCountStmt.QueryRow(setId)
		row.Scan(&photosCount)

		updateSetStmt.Exec(photosCount, taken, id, setId)
		fmt.Fprintf(os.Stderr, "sets id=%d photos_count=%d taken=%q thumb_photo_id=%d\n", setId, photosCount, taken.String, id)
	}

	if err := rows.Err(); err != nil {
		return err
	}

	if err := tx.Commit(); err != nil {
		return err
	}

	return nil
}

func loadSchema(db *sql.DB) (err error) {
	_, err = db.Exec(`
	CREATE TABLE IF NOT EXISTS sets (
	  id integer NOT NULL PRIMARY KEY,
	  thumb_photo_id integer UNIQUE REFERENCES photos,
	  name varchar(4096) NOT NULL UNIQUE,
	  photos_count integer,
	  taken_at datetime
	);

	CREATE INDEX IF NOT EXISTS sets_thumb_photo_id_index ON sets (thumb_photo_id);

	CREATE TABLE IF NOT EXISTS photos (
	  id integer NOT NULL PRIMARY KEY,
	  set_id integer NOT NULL REFERENCES sets,
	  prev_photo_id integer UNIQUE REFERENCES photos,
	  next_photo_id integer UNIQUE REFERENCES photos,
	  path varchar(4096) NOT NULL UNIQUE,
	  size integer NOT NULL,
	  width integer NOT NULL,
	  height integer NOT NULL,
	  aperture decimal(2, 1),
	  camera varchar(1000),
	  exposure_comp integer,
	  exposure_time decimal(9, 5),
	  flash varchar(51),
	  focal_length decimal(3, 1),
	  focal_length_35 integer,
	  iso integer,
	  lat decimal(9, 6),
	  lens varchar(1000),
	  lng decimal(9, 6),
	  taken_at datetime
	);

	CREATE INDEX IF NOT EXISTS photos_set_id_index ON photos (set_id);

	CREATE INDEX IF NOT EXISTS photos_prev_photo_id_index ON photos (prev_photo_id);

	CREATE INDEX IF NOT EXISTS photos_next_photo_id_index ON photos (next_photo_id);
	`)

	return
}

func main() {
	var err error

	if len(os.Args) == 1 {
		fmt.Fprintf(os.Stderr, "usage: %v path [path...]\n", os.Args[0])
		return
	}

	db, err = sql.Open("sqlite3", "thyme.db")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	if loadSchema(db) != nil {
		log.Fatal(err)
	}

	selectSetStmt, err = db.Prepare("SELECT id FROM sets WHERE name = ?")
	if err != nil {
		log.Fatal(err)
	}
	defer selectSetStmt.Close()

	selectPhotoStmt, err = db.Prepare("SELECT id FROM photos WHERE path = ?")
	if err != nil {
		log.Fatal(err)
	}
	defer selectPhotoStmt.Close()

	insertSetStmt, err = db.Prepare("INSERT INTO sets (name) VALUES (?)")
	if err != nil {
		log.Fatal(err)
	}
	defer insertSetStmt.Close()

	insertPhotoStmt, err = db.Prepare(`
	INSERT INTO photos (
	aperture, camera, exposure_comp, exposure_time, flash, focal_length,
	focal_length_35, height, iso, lat, lens, lng, path, set_id, size, taken_at,
	width
	)
	VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
	`)
	if err != nil {
		log.Fatal(err)
	}
	defer insertPhotoStmt.Close()

	for i := 1; i < len(os.Args); i++ {
		filepath.Walk(os.Args[i], walk)
	}

	updatePhotoSiblings()
	updateSets()
}
<commit_msg>Disassociate folder from photo<commit_after>package main

import (
	"database/sql"
	"fmt"
	"image"
	_ "image/jpeg"
	"log"
	"mime"
	"os"
	"path/filepath"

	"github.com/agorf/goexif/exif"
	_ "github.com/mattn/go-sqlite3"
)

type Photo struct {
	Aperture      sql.NullFloat64
	Camera        sql.NullString
	ExposureComp  sql.NullInt64
	ExposureTime  sql.NullFloat64
	Flash         sql.NullString
	FocalLength   sql.NullFloat64
	FocalLength35 sql.NullInt64
	Height        int
	ISO           sql.NullInt64
	Lat           sql.NullFloat64
	Lens          sql.NullString
	Lng           sql.NullFloat64
	Path          string
	Size          int64
	Taken         sql.NullString
	Width         int
}

var (
	db                                                             *sql.DB
	selectSetStmt, selectPhotoStmt, insertSetStmt, insertPhotoStmt *sql.Stmt
)

func decodePhotoExif(photo *Photo, x *exif.Exif) {
	taken, err := x.DateTime()
	if err == nil {
		photo.Taken.String = taken.UTC().Format("2006-01-02 15:04:05")
		photo.Taken.Valid = true
	}

	lat, lng, err := x.LatLong()
	if err == nil {
		photo.Lat.Float64 = lat
		photo.Lat.Valid = true
		photo.Lng.Float64 = lng
		photo.Lng.Valid = true
	}

	orientTag, err := x.Get(exif.Orientation)
	if err == nil {
		switch orient, _ := orientTag.Int(0); orient {
		case 5, 6, 7, 8: // rotated
			photo.Width, photo.Height = photo.Height, photo.Width // swap
		}
	}

	camMakeTag, err := x.Get(exif.Make)
	if err == nil {
		photo.Camera.String, _ = camMakeTag.StringVal()
		photo.Camera.Valid = true
	}

	camModelTag, err := x.Get(exif.Model)
	if err == nil {
		cameraModel, _ := camModelTag.StringVal()

		if photo.Camera.Valid {
			photo.Camera.String = fmt.Sprint(photo.Camera.String, " ", cameraModel)
		} else {
			photo.Camera.String = cameraModel
			photo.Camera.Valid = true
		}
	}

	lensMakeTag, err := x.Get(exif.LensMake)
	if err == nil {
		photo.Lens.String, _ = lensMakeTag.StringVal()
		photo.Lens.Valid = true
	}

	lensModelTag, err := x.Get(exif.LensModel)
	if err == nil {
		lensModel, _ := lensModelTag.StringVal()

		if photo.Lens.Valid {
			photo.Lens.String = fmt.Sprint(photo.Lens.String, " ", lensModel)
		} else {
			photo.Lens.String = lensModel
			photo.Lens.Valid = true
		}
	}

	focalLenTag, err := x.Get(exif.FocalLength)
	if err == nil {
		focalLen, _ := focalLenTag.Rat(0)
		photo.FocalLength.Float64, _ = focalLen.Float64()
		photo.FocalLength.Valid = true
	}

	focalLen35Tag, err := x.Get(exif.FocalLengthIn35mmFilm)
	if err == nil {
		photo.FocalLength35.Int64, _ = focalLen35Tag.Int64(0)
		photo.FocalLength35.Valid = true
	}

	apertureTag, err := x.Get(exif.FNumber)
	if err == nil {
		aperture, _ := apertureTag.Rat(0)
		photo.Aperture.Float64, _ = aperture.Float64()
		photo.Aperture.Valid = true
	}

	expTimeTag, err := x.Get(exif.ExposureTime)
	if err == nil {
		expTime, _ := expTimeTag.Rat(0)
		photo.ExposureTime.Float64, _ = expTime.Float64()
		photo.ExposureTime.Valid = true
	}

	isoTag, err := x.Get(exif.ISOSpeedRatings)
	if err == nil {
		photo.ISO.Int64, _ = isoTag.Int64(0)
		photo.ISO.Valid = true
	}

	expBiasTag, err := x.Get(exif.ExposureBiasValue)
	if err == nil {
		photo.ExposureComp.Int64, _ = expBiasTag.Int64(0)
		photo.ExposureComp.Valid = true
	}

	flash, err := x.Flash()
	if err == nil {
		photo.Flash.String = flash
		photo.Flash.Valid = true
	}
}

func decodePhoto(path string) (*Photo, error) {
	var photo Photo

	photo.Path = path

	f, err := os.Open(path)
	if err != nil {
		return &photo, err
	}
	defer f.Close()

	fi, err := f.Stat()
	if err != nil {
		return &photo, err
	}
	photo.Size = fi.Size()

	img, _, err := image.DecodeConfig(f)
	if err != nil {
		return &photo, err
	}
	photo.Width, photo.Height = img.Width, img.Height

	f.Seek(0, 0) // rewind

	x, err := exif.Decode(f)
	if err == nil { // EXIF data exists
		decodePhotoExif(&photo, x)
	}

	return &photo, nil
}

func storePhoto(photo *Photo) error {
	var setId, photoId int64

	setName := filepath.Base(filepath.Dir(photo.Path))
	row := selectSetStmt.QueryRow(setName)
	if err := row.Scan(&setId); err == sql.ErrNoRows { // set does not exist
		result, err := insertSetStmt.Exec(setName) // create it
		if err != nil {
			return err
		}

		setId, err = result.LastInsertId()
		if err != nil {
			return err
		}
	}

	row = selectPhotoStmt.QueryRow(photo.Path)
	if err := row.Scan(&photoId); err == sql.ErrNoRows { // photo does not exist
		result, err := insertPhotoStmt.Exec(photo.Aperture, photo.Camera,
			photo.ExposureComp, photo.ExposureTime, photo.Flash, photo.FocalLength,
			photo.FocalLength35, photo.Height, photo.ISO, photo.Lat, photo.Lens,
			photo.Lng, photo.Path, setId, photo.Size, photo.Taken, photo.Width) // create it
		if err != nil {
			return err
		}

		photoId, err = result.LastInsertId()
		if err != nil {
			return err
		}

		fmt.Fprintf(os.Stderr, "photos id=%d path=%s\n", photoId, photo.Path)
	}

	return nil
}

func walk(path string, info os.FileInfo, err error) error {
	if err != nil { // error walking "path"
		return nil // skip
	}

	if info.IsDir() {
		return nil // skip
	}

	if mime.TypeByExtension(filepath.Ext(path)) != "image/jpeg" { // not JPEG
		return nil // skip
	}

	photo, err := decodePhoto(path)
	if err == nil {
		storePhoto(photo)
	}

	return nil // next
}

func updatePhotoSiblings() error {
	var prevId, prevSetId int

	tx, err := db.Begin()
	if err != nil {
		return err
	}

	updatePrevPhotoStmt, err := tx.Prepare(`
	UPDATE photos SET prev_photo_id = ? WHERE id = ?
	`)
	if err != nil {
		return err
	}
	defer updatePrevPhotoStmt.Close()

	updateNextPhotoStmt, err := tx.Prepare(`
	UPDATE photos SET next_photo_id = ? WHERE id = ?
	`)
	if err != nil {
		return err
	}
	defer updateNextPhotoStmt.Close()

	rows, err := tx.Query(`
	SELECT id, set_id FROM photos ORDER BY set_id, taken_at
	`)
	if err != nil {
		return err
	}
	defer rows.Close()

	for rows.Next() {
		var id, setId int
		rows.Scan(&id, &setId)

		if setId == prevSetId && prevId > 0 {
			updatePrevPhotoStmt.Exec(prevId, id)
			fmt.Fprintf(os.Stderr, "photos id=%d prev_photo_id=%d\n", id, prevId)
			updateNextPhotoStmt.Exec(id, prevId)
			fmt.Fprintf(os.Stderr, "photos id=%d next_photo_id=%d\n", prevId, id)
		}

		prevId = id
		prevSetId = setId
	}

	if err := rows.Err(); err != nil {
		return err
	}

	if err := tx.Commit(); err != nil {
		return err
	}

	return nil
}

func updateSets() error {
	tx, err := db.Begin()
	if err != nil {
		return err
	}

	photosCountStmt, err := tx.Prepare(`
	SELECT COUNT(*) FROM photos WHERE set_id = ?
	`)
	if err != nil {
		return err
	}
	defer photosCountStmt.Close()

	updateSetStmt, err := tx.Prepare(`
	UPDATE sets
	SET photos_count = ?, taken_at = ?, thumb_photo_id = ?
	WHERE id = ?
	`)
	if err != nil {
		return err
	}
	defer updateSetStmt.Close()

	rows, err := tx.Query(`
	SELECT id, set_id, MIN(taken_at) FROM photos GROUP BY set_id
	`)
	if err != nil {
		return err
	}
	defer rows.Close()

	for rows.Next() {
		var id, setId, photosCount int
		var taken sql.NullString

		rows.Scan(&id, &setId, &taken)

		row := photosCountStmt.QueryRow(setId)
		row.Scan(&photosCount)

		updateSetStmt.Exec(photosCount, taken, id, setId)
		fmt.Fprintf(os.Stderr, "sets id=%d photos_count=%d taken=%q thumb_photo_id=%d\n", setId, photosCount, taken.String, id)
	}

	if err := rows.Err(); err != nil {
		return err
	}

	if err := tx.Commit(); err != nil {
		return err
	}

	return nil
}

func loadSchema(db *sql.DB) (err error) {
	_, err = db.Exec(`
	CREATE TABLE IF NOT EXISTS sets (
	  id integer NOT NULL PRIMARY KEY,
	  thumb_photo_id integer UNIQUE REFERENCES photos,
	  name varchar(4096) NOT NULL UNIQUE,
	  photos_count integer,
	  taken_at datetime
	);

	CREATE INDEX IF NOT EXISTS sets_thumb_photo_id_index ON sets (thumb_photo_id);

	CREATE TABLE IF NOT EXISTS photos (
	  id integer NOT NULL PRIMARY KEY,
	  set_id integer NOT NULL REFERENCES sets,
	  prev_photo_id integer UNIQUE REFERENCES photos,
	  next_photo_id integer UNIQUE REFERENCES photos,
	  path varchar(4096) NOT NULL UNIQUE,
	  size integer NOT NULL,
	  width integer NOT NULL,
	  height integer NOT NULL,
	  aperture decimal(2, 1),
	  camera varchar(1000),
	  exposure_comp integer,
	  exposure_time decimal(9, 5),
	  flash varchar(51),
	  focal_length decimal(3, 1),
	  focal_length_35 integer,
	  iso integer,
	  lat decimal(9, 6),
	  lens varchar(1000),
	  lng decimal(9, 6),
	  taken_at datetime
	);

	CREATE INDEX IF NOT EXISTS photos_set_id_index ON photos (set_id);

	CREATE INDEX IF NOT EXISTS photos_prev_photo_id_index ON photos (prev_photo_id);

	CREATE INDEX IF NOT EXISTS photos_next_photo_id_index ON photos (next_photo_id);
	`)

	return
}

func main() {
	var err error

	if len(os.Args) == 1 {
		fmt.Fprintf(os.Stderr, "usage: %v path [path...]\n", os.Args[0])
		return
	}

	db, err = sql.Open("sqlite3", "thyme.db")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	if loadSchema(db) != nil {
		log.Fatal(err)
	}

	selectSetStmt, err = db.Prepare("SELECT id FROM sets WHERE name = ?")
	if err != nil {
		log.Fatal(err)
	}
	defer selectSetStmt.Close()

	selectPhotoStmt, err = db.Prepare("SELECT id FROM photos WHERE path = ?")
	if err != nil {
		log.Fatal(err)
	}
	defer selectPhotoStmt.Close()

	insertSetStmt, err = db.Prepare("INSERT INTO sets (name) VALUES (?)")
	if err != nil {
		log.Fatal(err)
	}
	defer insertSetStmt.Close()

	insertPhotoStmt, err = db.Prepare(`
	INSERT INTO photos (
	aperture, camera, exposure_comp, exposure_time, flash, focal_length,
	focal_length_35, height, iso, lat, lens, lng, path, set_id, size, taken_at,
	width
	)
	VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
	`)
	if err != nil {
		log.Fatal(err)
	}
	defer insertPhotoStmt.Close()

	for i := 1; i < len(os.Args); i++ {
		filepath.Walk(os.Args[i], walk)
	}

	updatePhotoSiblings()
	updateSets()
}
<|endoftext|>


<commit_before>package git

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"time"

	"golang.org/x/sys/unix"
)

func SetMTimes(git_dir, checkout_dir, ref string) error {
	// From https://github.com/rosylilly/git-set-mtime with tweaks
	// Copyright (c) 2014 Sho Kusano

	// MIT License

	// 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.

	lsFiles := Command(git_dir, "git", "ls-tree", "-z", "-r", "--name-only", ref)
	lsFiles.Stdout = nil
	out, err := lsFiles.Output()
	if err != nil {
		return fmt.Errorf("git ls-files: %v", err)
	}

	dirMTimes := map[string]time.Time{}

	chtime := func(path string, atime, mtime time.Time) error {
		// https://github.com/torvalds/linux/blob/2decb2682f80759f631c8332f9a2a34a02150a03/include/uapi/linux/fcntl.h#L56
		var utimes [2]unix.Timespec
		utimes[0] = unix.NsecToTimespec(atime.UnixNano())
		utimes[1] = unix.NsecToTimespec(mtime.UnixNano())

		if e := unix.UtimesNanoAt(unix.AT_FDCWD, path, utimes[0:], unix.AT_SYMLINK_NOFOLLOW); e != nil {
			return &os.PathError{"futimesat", path, e}
		}
		return nil
	}

	files := strings.Split(strings.TrimRight(string(out), "\x00"), "\x00")
	for _, file := range files {
		gitLog := Command(git_dir, "git", "log", "-1", "--date=rfc2822",
			"--format=%cd", ref, "--", file)
		gitLog.Stdout = nil

		out, err := gitLog.Output()
		if err != nil {
			return fmt.Errorf("git log: %v", err)
		}

		mStr := strings.TrimSpace(strings.TrimLeft(string(out), "Date:"))
		mTime, err := time.Parse("Mon, 2 Jan 2006 15:04:05 -0700", mStr)
		if err != nil {
			return fmt.Errorf("parse time: %v", err)
		}

		// Loop over each directory in the path to `file`, updating `dirMTimes`
		// to take the most recent time seen.
		dir := filepath.Dir(file)
		for {
			if other, ok := dirMTimes[dir]; ok {
				if mTime.After(other) {
					// file mTime is more recent than previous seen for 'dir'
					dirMTimes[dir] = mTime
				}
			} else {
				// first occurrence of dir
				dirMTimes[dir] = mTime
			}

			// Remove one directory from the path until it isn't changed anymore
			if dir == filepath.Dir(dir) {
				break
			}
			dir = filepath.Dir(dir)
		}

		err = chtime(checkout_dir+"/"+file, mTime, mTime)
		if err != nil {
			return fmt.Errorf("chtimes: %v", err)
		}

		fmt.Printf("%s: %s\n", file, mTime)
	}

	for dir, mTime := range dirMTimes {
		err = chtime(checkout_dir+"/"+dir, mTime, mTime)
		if err != nil {
			return fmt.Errorf("chtimes: %v", err)
		}
		fmt.Printf("%s: %s\n", dir, mTime)
	}
	return nil
}
<commit_msg>Remove prints from SetMTimes<commit_after>package git

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"time"

	"golang.org/x/sys/unix"
)

func SetMTimes(git_dir, checkout_dir, ref string) error {
	// From https://github.com/rosylilly/git-set-mtime with tweaks
	// Copyright (c) 2014 Sho Kusano

	// MIT License

	// 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.

	lsFiles := Command(git_dir, "git", "ls-tree", "-z", "-r", "--name-only", ref)
	lsFiles.Stdout = nil
	out, err := lsFiles.Output()
	if err != nil {
		return fmt.Errorf("git ls-files: %v", err)
	}

	dirMTimes := map[string]time.Time{}

	chtime := func(path string, atime, mtime time.Time) error {
		// https://github.com/torvalds/linux/blob/2decb2682f80759f631c8332f9a2a34a02150a03/include/uapi/linux/fcntl.h#L56
		var utimes [2]unix.Timespec
		utimes[0] = unix.NsecToTimespec(atime.UnixNano())
		utimes[1] = unix.NsecToTimespec(mtime.UnixNano())

		if e := unix.UtimesNanoAt(unix.AT_FDCWD, path, utimes[0:], unix.AT_SYMLINK_NOFOLLOW); e != nil {
			return &os.PathError{"futimesat", path, e}
		}
		return nil
	}

	files := strings.Split(strings.TrimRight(string(out), "\x00"), "\x00")
	for _, file := range files {
		gitLog := Command(git_dir, "git", "log", "-1", "--date=rfc2822",
			"--format=%cd", ref, "--", file)
		gitLog.Stdout = nil

		out, err := gitLog.Output()
		if err != nil {
			return fmt.Errorf("git log: %v", err)
		}

		mStr := strings.TrimSpace(strings.TrimLeft(string(out), "Date:"))
		mTime, err := time.Parse("Mon, 2 Jan 2006 15:04:05 -0700", mStr)
		if err != nil {
			return fmt.Errorf("parse time: %v", err)
		}

		// Loop over each directory in the path to `file`, updating `dirMTimes`
		// to take the most recent time seen.
		dir := filepath.Dir(file)
		for {
			if other, ok := dirMTimes[dir]; ok {
				if mTime.After(other) {
					// file mTime is more recent than previous seen for 'dir'
					dirMTimes[dir] = mTime
				}
			} else {
				// first occurrence of dir
				dirMTimes[dir] = mTime
			}

			// Remove one directory from the path until it isn't changed anymore
			if dir == filepath.Dir(dir) {
				break
			}
			dir = filepath.Dir(dir)
		}

		err = chtime(checkout_dir+"/"+file, mTime, mTime)
		if err != nil {
			return fmt.Errorf("chtimes: %v", err)
		}
	}

	for dir, mTime := range dirMTimes {
		err = chtime(checkout_dir+"/"+dir, mTime, mTime)
		if err != nil {
			return fmt.Errorf("chtimes: %v", err)
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package Milena

import (
	"gopkg.in/Shopify/sarama.v1"
	"github.com/JodeZer/Milena/log"
	"fmt"
)

var iF = func(b bool, l interface{}, r interface{}) interface{} {
	if b {
		return l
	}
	return r
}

type topicWorkerConfig struct {
	TopicName         string
	Partions          []partionSetting
	DataDir           string
	ClusterNameBelong string
}
type topicWorker struct {
	c                 *topicWorkerConfig
	mEngine           metaStorageEngine
	topickey          string
	tEngine           topicStorageEngine
	topicFileName     string
	consumer          sarama.Consumer
	pConsumer         []sarama.PartitionConsumer
	partionKeys       map[int32]string
	partitionSettings map[int32]*partionSetting
}

func newTopicWorker(consumer sarama.Consumer, metaDB metaStorageEngine, conf *topicWorkerConfig) *topicWorker {
	t := &topicWorker{c:conf, mEngine:metaDB}
	t.topickey = t.c.ClusterNameBelong + "." + t.c.TopicName
	t.topicFileName = t.c.DataDir + "/" + t.c.TopicName + ".log"
	t.consumer = consumer
	t.partitionSettings = make(map[int32]*partionSetting, len(conf.Partions))
	for _, p := range conf.Partions {
		t.partitionSettings[p.Partition] = &p
	}
	t.tEngine = newTopicSimpleStorageEngine(&topicStorageConfig{t.topicFileName})
	return t
}

func (t *topicWorker) Run() {
	for k, v := range t.partitionSettings {
		dbOff := t.mEngine.GetOffset(genPartitionkey(t.topickey, k))
		if v.Start < dbOff {
			t.partitionSettings[k].Start = dbOff
		}
	}

	ps, err := t.consumer.Partitions(t.c.TopicName)
	if err != nil {
		log.Errorf("%s", err)
		log.Degbugf("%s", err)
		return
	}
	t.partionKeys = make(map[int32]string, len(ps))

	for _, p := range ps {
		pc, err := t.consumer.ConsumePartition(t.c.TopicName, p, t.partitionSettings[p].Start)
		t.partionKeys[p] = t.topickey + "~" + fmt.Sprintf("%d", p)
		if err != nil {
			log.Errorf("%s", err)
			log.Degbugf("%s", err)
			continue
		}
		t.pConsumer = append(t.pConsumer, pc)
	}
	log.Degbugf("%v", t.partionKeys)
	go t.readLoop()

}

func (t *topicWorker) Stop() {

}

func (t *topicWorker)readLoop() {
	msgChan := make(chan *sarama.ConsumerMessage, 1000)
	func() {
		for _, pc := range t.pConsumer {
			go func(spc sarama.PartitionConsumer) {
				for {
					msg := <-spc.Messages()
					msgChan <- msg//TODO timeout
				}
			}(pc)
		}
	}()
	for msg := range msgChan {
		log.Degbugf(genLineMsg(msg))//TODO storage
		if err := t.tEngine.Append(msg); err != nil {
			log.Errorf("append fail %s", err)
			continue
		}
		_ = t.partionKeys[msg.Partition]
		t.mEngine.UpdateOffset(t.partionKeys[msg.Partition], msg.Offset + 1)
	}
}

func genPartitionkey(topicKey string, p int32) string {
	return topicKey + "~" + fmt.Sprintf("%d", p)
}<commit_msg>fix offset start error<commit_after>package Milena

import (
	"gopkg.in/Shopify/sarama.v1"
	"github.com/JodeZer/Milena/log"
	"fmt"
	"time"
)

var iF = func(b bool, l interface{}, r interface{}) interface{} {
	if b {
		return l
	}
	return r
}

type topicWorkerConfig struct {
	TopicName         string
	Partions          []partionSetting
	DataDir           string
	ClusterNameBelong string
}
type topicWorker struct {
	c                 *topicWorkerConfig
	mEngine           metaStorageEngine
	topickey          string
	tEngine           topicStorageEngine
	topicFileName     string
	consumer          sarama.Consumer
	pConsumer         []sarama.PartitionConsumer
	partionKeys       map[int32]string
	partitionSettings map[int32]*partionSetting
	stopC             StopChan
}

func newTopicWorker(consumer sarama.Consumer, metaDB metaStorageEngine, conf *topicWorkerConfig) *topicWorker {
	t := &topicWorker{c:conf, mEngine:metaDB}
	t.topickey = t.c.ClusterNameBelong + "." + t.c.TopicName
	t.topicFileName = t.c.DataDir + "/" + t.c.TopicName + ".log"
	t.consumer = consumer
	t.partitionSettings = make(map[int32]*partionSetting, len(conf.Partions))
	for _, p := range conf.Partions {
		t.partitionSettings[p.Partition] = &p
	}
	t.tEngine = newTopicSimpleStorageEngine(&topicStorageConfig{t.topicFileName})
	t.stopC = make(StopChan, 1)
	return t
}

func (t *topicWorker) Run() {
	ps, err := t.consumer.Partitions(t.c.TopicName)
	if err != nil {
		log.Errorf("%s", err)
		log.Degbugf("%s", err)
		return
	}
	t.partionKeys = make(map[int32]string, len(ps))

	for _, p := range ps {
		dbOff := t.mEngine.GetOffset(genPartitionkey(t.topickey, p))
		pconf, ok := t.partitionSettings[p]
		if !ok {
			pconf = &partionSetting{Start:0}
		}
		if pconf.Start < dbOff {
			pconf.Start = dbOff
		}
		pc, err := t.consumer.ConsumePartition(t.c.TopicName, p, pconf.Start)
		t.partionKeys[p] = t.topickey + "~" + fmt.Sprintf("%d", p)
		if err != nil {
			log.Errorf("%s", err)
			log.Degbugf("%s", err)
			continue
		}
		t.pConsumer = append(t.pConsumer, pc)
	}
	log.Degbugf("%v", t.partionKeys)
	go t.readLoop()

}

func (t *topicWorker) Stop() {
	for i := 0; i < len(t.pConsumer) + 1; i++ {
		t.stopC <- stopSig{}
	}
	//wait all exit
	time.Sleep(1 * time.Second)
	t.mEngine.Close()
	t.tEngine.Close()
}

func (t *topicWorker)readLoop() {
	msgChan := make(chan *sarama.ConsumerMessage, 1000)
	func() {
		for i, pc := range t.pConsumer {
			go func(spc sarama.PartitionConsumer, index int) {
				for {

					select {
					case msg := <-spc.Messages():
						msgChan <- msg//TODO timeout
					case <-t.stopC:
						spc.Close()
						log.Degbugf("%s~%d rcv stop sig and stop", t.topickey, index)
						return
					case err := <-spc.Errors():
						log.Errorf("%s %s", t.topickey, err)
					}
				}
			}(pc, i)
		}
	}()
	for {
		select {
		case msg := <-msgChan:
			log.Degbugf(genLineMsg(msg))//TODO storage
			if err := t.tEngine.Append(msg); err != nil {
				log.Errorf("append fail %s", err)
				continue
			}
			t.mEngine.UpdateOffset(t.partionKeys[msg.Partition], msg.Offset + 1)
		case <-t.stopC:
			log.Degbugf("%s rcv stop sig and stop write", t.c.TopicName)
			return
		}
	}
}

func genPartitionkey(topicKey string, p int32) string {
	return topicKey + "~" + fmt.Sprintf("%d", p)
}<|endoftext|>


<commit_before>package ncp

import (
	"regexp"
	"strconv"
	"strings"
)

func (t *Topic) getRating() float64 {
	var (
		reRating = regexp.MustCompile(`>(\d,\d|\d)<\/span>.+?\(Голосов:`)
		rating   float64
	)
	if reRating.Match(t.Body) == true {
		str := string(reRating.FindSubmatch(t.Body)[1])
		str = strings.Replace(str, ",", ".", -1)
		rating, _ = strconv.ParseFloat(str, 64)
	}
	return rating
}

func (t *Topic) getSize() int64 {
	var (
		reSize = regexp.MustCompile(`Размер блока: \d.+?B"> (\d{1,2},\d{1,2}|\d{3,4}|\d{1,2})\s`)
		size   int64
	)
	if reSize.Match(t.Body) == true {
		str := string(reSize.FindSubmatch(t.Body)[1])
		str = strings.Replace(str, ",", ".", -1)
		if s64, err := strconv.ParseFloat(str, 64); err == nil {
			if s64 < 100 {
				s64 = s64 * 1000
			}
			size = int64(s64)
		}
	}
	return size
}

func (t *Topic) getTorrent() string {
	var (
		reTor   = regexp.MustCompile(`<a href="download\.php\?id=(\d{5,7})" rel="nofollow">Скачать<`)
		torrent string
	)
	if reTor.Match(t.Body) == true {
		findTor := reTor.FindSubmatch(t.Body)
		torrent = string(findTor[1])
		// http://nnm-club.me/forum/download.php?id=
	}
	return torrent
}

func (t *Topic) getMagnet() string {
	var (
		reMag  = regexp.MustCompile(`href="magnet:\?xt=urn:btih:(.+?)(?:"|&)`)
		magnet string
	)
	if reMag.Match(t.Body) == true {
		findMag := reMag.FindSubmatch(t.Body)
		magnet = string(findMag[1])
		// magnet:?xt=urn:btih:
	}
	return magnet
}

func (t *Topic) getPoster() string {
	var (
		rePos = regexp.MustCompile(`"postImg postImgAligned img-right" title="http:\/\/assets\.nnm-club\.ws\/forum\/image\.php\?link=(.+?jpe{0,1}g)`)
		image string
	)
	if rePos.Match(t.Body) == true {
		image = string(rePos.FindSubmatch(t.Body)[1])
	}
	return image
}

func (t *Topic) getDate() string {
	var (
		reDate = regexp.MustCompile(`> (\d{1,2} .{3} \d{4}).{9}<`)
		date   string
	)
	if reDate.Match(t.Body) == true {
		date = replaceDate(string(reDate.FindSubmatch(t.Body)[1]))
	}
	return date
}

func (t *Topic) getSeeds() int64 {
	var (
		reSs  = regexp.MustCompile(`<span class="seed">\[ <b>(\d{1,5})\s`)
		seeds int64
	)
	if reSs.Match(t.Body) == true {
		ss := reSs.FindSubmatch(t.Body)
		seeds, _ = strconv.ParseInt(string(ss[1]), 10, 64)
	}
	return seeds
}

func (t *Topic) getLeechs() int64 {
	var (
		reLs   = regexp.MustCompile(`<span class="leech">\[ <b>(\d{1,5})\s`)
		leechs int64
	)
	if reLs.Match(t.Body) == true {
		ls := reLs.FindSubmatch(t.Body)
		leechs, _ = strconv.ParseInt(string(ls[1]), 10, 64)
	}
	return leechs
}

func getResolution(str string) string {
	var (
		reRes      = regexp.MustCompile(`(\d{3,4}x\d{3,4}|\d{3,4}X\d{3,4}|\d{3,4}х\d{3,4}|\d{3,4}Х\d{3,4})`)
		resolution string
	)
	if reRes.MatchString(str) == true {
		resolution = reRes.FindString(str)
	}
	return resolution
}

func (t *Topic) getCountry() string {
	var (
		reCountry = regexp.MustCompile(`<span style="font-weight: bold">Производство:\s*<\/span>(.+?)<`)
		country   string
	)
	if reCountry.Match(t.Body) == true {
		country = string(reCountry.FindSubmatch(t.Body)[1])
		country = cleanStr(country)
	}
	return country
}

func (t *Topic) getGenre() string {
	var (
		reGenre = regexp.MustCompile(`<span style="font-weight: bold">Жанр:\s*<\/span>(.+?)<`)
		genre   string
	)
	if reGenre.Match(t.Body) == true {
		genre = string(reGenre.FindSubmatch(t.Body)[1])
		genre = strings.ToLower(cleanStr(genre))
		genre = strings.Trim(genre, ".")
	}
	return genre
}

func (t *Topic) getDirector() string {
	var (
		reDirector = regexp.MustCompile(`<span style="font-weight: bold">Режиссер:\s*<\/span>(.+?)<`)
		director   string
	)
	if reDirector.Match(t.Body) == true {
		director = string(reDirector.FindSubmatch(t.Body)[1])
		director = cleanStr(director)
		director = strings.Trim(director, ".")
	}
	return director
}

func (t *Topic) getProducer() string {
	var (
		reProducer = regexp.MustCompile(`<span style="font-weight: bold">Продюсер:\s*<\/span>(.+?)<`)
		producer   string
	)
	if reProducer.Match(t.Body) == true {
		producer = string(reProducer.FindSubmatch(t.Body)[1])
		producer = cleanStr(producer)
		producer = strings.Trim(producer, ".")
	}
	return producer
}

func (t *Topic) getActors() string {
	var (
		reActors = regexp.MustCompile(`<span style="font-weight: bold">Актеры:\s*<\/span>(.+?)<`)
		actors   string
	)
	if reActors.Match(t.Body) == true {
		actors = string(reActors.FindSubmatch(t.Body)[1])
		actors = cleanStr(actors)
		actors = strings.Trim(actors, ".")
	}
	return actors
}

func (t *Topic) getDescription() string {
	var (
		reDescription = regexp.MustCompile(`<span style="font-weight: bold">(?:Описание фильма|Описание):\s*<\/span>(.+?)<`)
		description   string
	)
	if reDescription.Match(t.Body) == true {
		description = string(reDescription.FindSubmatch(t.Body)[1])
		description = cleanStr(description)
	}
	return description
}

func (t *Topic) getAge() string {
	var (
		reAge = regexp.MustCompile(`<span style="font-weight: bold">Возраст:\s*<\/span>(.+?)<`)
		age   string
	)
	if reAge.Match(t.Body) == true {
		age = string(reAge.FindSubmatch(t.Body)[1])
		age = cleanStr(age)
	}
	return age
}

func (t *Topic) getReleaseDate() string {
	var (
		reReleaseDate = regexp.MustCompile(`<span style="font-weight: bold">Дата мировой премьеры:\s*<\/span>(.+?)<`)
		releaseDate   string
	)
	if reReleaseDate.Match(t.Body) == true {
		releaseDate = string(reReleaseDate.FindSubmatch(t.Body)[1])
		releaseDate = cleanStr(releaseDate)
		releaseDate = replaceDate(releaseDate)
	}
	return releaseDate
}

func (t *Topic) getRussianDate() string {
	var (
		reRussianDate = regexp.MustCompile(`<span style="font-weight: bold">(?:Дата премьеры в России|Дата Российской премьеры|Дата российской премьеры):\s*<\/span>(.+?)<`)
		russianDate   string
	)
	if reRussianDate.Match(t.Body) == true {
		russianDate = string(reRussianDate.FindSubmatch(t.Body)[1])
		russianDate = cleanStr(russianDate)
		russianDate = replaceDate(russianDate)
	}
	return russianDate
}

func (t *Topic) getDuration() string {
	var (
		reDuration = regexp.MustCompile(`<span style="font-weight: bold">Продолжительность:\s*<\/span>(.+?)<`)
		duration   string
	)
	if reDuration.Match(t.Body) == true {
		duration = string(reDuration.FindSubmatch(t.Body)[1])
		duration = cleanStr(duration)
	} else {
		reDuration = regexp.MustCompile(`\sПродолжительность\s+?: (\d{1,2}) ч\. (\d{1,2}) м\.`)
		if reDuration.Match(t.Body) == true {
			submatch := reDuration.FindSubmatch(t.Body)
			hour := string(submatch[1])
			minute := string(submatch[2])
			if len(hour) == 1 {
				hour = "0" + hour
			}
			if len(minute) == 1 {
				minute = "0" + minute
			}
			duration = hour + ":" + minute + ":00"
		}
	}
	if len(duration) < 5 {
		duration = ""
	}
	return duration
}

func (t *Topic) getQuality() string {
	var (
		reQuality = regexp.MustCompile(`<span style="font-weight: bold">(?:Качество видео|Качество):\s*<\/span>(.+?)<`)
		quality   string
	)
	if reQuality.Match(t.Body) == true {
		quality = string(reQuality.FindSubmatch(t.Body)[1])
		quality = cleanStr(quality)
	}
	return quality
}

func (t *Topic) getTranslation() string {
	var (
		reTranslation = regexp.MustCompile(`<span style="font-weight: bold">Перевод:\s*<\/span>(.+?)<`)
		translation   string
	)
	translation = "Не требуется"
	if reTranslation.Match(t.Body) == true {
		translation = string(reTranslation.FindSubmatch(t.Body)[1])
		translation = cleanStr(translation)
	}
	return translation
}

func (t *Topic) getSubtitlesType() string {
	var (
		reSubtitlesType = regexp.MustCompile(`<span style="font-weight: bold">Вид субтитров:\s*<\/span>(.+?)<`)
		subtitlesType   string
	)
	if reSubtitlesType.Match(t.Body) == true {
		subtitlesType = string(reSubtitlesType.FindSubmatch(t.Body)[1])
		subtitlesType = cleanStr(subtitlesType)
	}
	return subtitlesType
}

func (t *Topic) getSubtitles() string {
	var (
		reSubtitles = regexp.MustCompile(`<span style="font-weight: bold">Субтитры:\s*<\/span>(.+?)<`)
		subtitles   string
	)
	if reSubtitles.Match(t.Body) == true {
		subtitles = string(reSubtitles.FindSubmatch(t.Body)[1])
		subtitles = cleanStr(subtitles)
	}
	return subtitles
}

func (t *Topic) getVideo() string {
	var (
		reVideo = regexp.MustCompile(`<span style="font-weight: bold">Видео:\s*<\/span>(.+?)<`)
		video   string
	)
	if reVideo.Match(t.Body) == true {
		video = string(reVideo.FindSubmatch(t.Body)[1])
		video = cleanStr(video)
	}
	return video
}

func (t *Topic) getAudio1() string {
	var (
		reAudio = regexp.MustCompile(`<span style="font-weight: bold">(?:Аудио\s?:\s*|Аудио\s?.?1.?:\s*)<\/span>(.+?)<`)
		audio   string
	)
	if reAudio.Match(t.Body) == true {
		audio = string(reAudio.FindSubmatch(t.Body)[1])
		audio = cleanStr(audio)
	}
	return audio
}

func (t *Topic) getAudio2() string {
	var (
		reAudio = regexp.MustCompile(`<span style="font-weight: bold">Аудио\s?.?2.?:\s*<\/span>(.+?)<`)
		audio   string
	)
	if reAudio.Match(t.Body) == true {
		audio = string(reAudio.FindSubmatch(t.Body)[1])
		audio = cleanStr(audio)
	}
	return audio
}

func (t *Topic) getAudio3() string {
	var (
		reAudio = regexp.MustCompile(`<span style="font-weight: bold">Аудио\s?.?3.?:\s*<\/span>(.+?)<`)
		audio   string
	)
	if reAudio.Match(t.Body) == true {
		audio = string(reAudio.FindSubmatch(t.Body)[1])
		audio = cleanStr(audio)
	}
	return audio
}
<commit_msg>fix parse poster<commit_after>package ncp

import (
	"regexp"
	"strconv"
	"strings"
)

func (t *Topic) getRating() float64 {
	var (
		reRating = regexp.MustCompile(`>(\d,\d|\d)<\/span>.+?\(Голосов:`)
		rating   float64
	)
	if reRating.Match(t.Body) == true {
		str := string(reRating.FindSubmatch(t.Body)[1])
		str = strings.Replace(str, ",", ".", -1)
		rating, _ = strconv.ParseFloat(str, 64)
	}
	return rating
}

func (t *Topic) getSize() int64 {
	var (
		reSize = regexp.MustCompile(`Размер блока: \d.+?B"> (\d{1,2},\d{1,2}|\d{3,4}|\d{1,2})\s`)
		size   int64
	)
	if reSize.Match(t.Body) == true {
		str := string(reSize.FindSubmatch(t.Body)[1])
		str = strings.Replace(str, ",", ".", -1)
		if s64, err := strconv.ParseFloat(str, 64); err == nil {
			if s64 < 100 {
				s64 = s64 * 1000
			}
			size = int64(s64)
		}
	}
	return size
}

func (t *Topic) getTorrent() string {
	var (
		reTor   = regexp.MustCompile(`<a href="download\.php\?id=(\d{5,7})" rel="nofollow">Скачать<`)
		torrent string
	)
	if reTor.Match(t.Body) == true {
		findTor := reTor.FindSubmatch(t.Body)
		torrent = string(findTor[1])
		// http://nnm-club.me/forum/download.php?id=
	}
	return torrent
}

func (t *Topic) getMagnet() string {
	var (
		reMag  = regexp.MustCompile(`href="magnet:\?xt=urn:btih:(.+?)(?:"|&)`)
		magnet string
	)
	if reMag.Match(t.Body) == true {
		findMag := reMag.FindSubmatch(t.Body)
		magnet = string(findMag[1])
		// magnet:?xt=urn:btih:
	}
	return magnet
}

func (t *Topic) getPoster() string {
	var (
		rePos = regexp.MustCompile(`"postImg postImgAligned img-right" title="http:\/\/assets\..*?\/forum\/image\.php\?link=(.+?jpe{0,1}g)`)
		image string
	)
	if rePos.Match(t.Body) == true {
		image = string(rePos.FindSubmatch(t.Body)[1])
	}
	return image
}

func (t *Topic) getDate() string {
	var (
		reDate = regexp.MustCompile(`> (\d{1,2} .{3} \d{4}).{9}<`)
		date   string
	)
	if reDate.Match(t.Body) == true {
		date = replaceDate(string(reDate.FindSubmatch(t.Body)[1]))
	}
	return date
}

func (t *Topic) getSeeds() int64 {
	var (
		reSs  = regexp.MustCompile(`<span class="seed">\[ <b>(\d{1,5})\s`)
		seeds int64
	)
	if reSs.Match(t.Body) == true {
		ss := reSs.FindSubmatch(t.Body)
		seeds, _ = strconv.ParseInt(string(ss[1]), 10, 64)
	}
	return seeds
}

func (t *Topic) getLeechs() int64 {
	var (
		reLs   = regexp.MustCompile(`<span class="leech">\[ <b>(\d{1,5})\s`)
		leechs int64
	)
	if reLs.Match(t.Body) == true {
		ls := reLs.FindSubmatch(t.Body)
		leechs, _ = strconv.ParseInt(string(ls[1]), 10, 64)
	}
	return leechs
}

func getResolution(str string) string {
	var (
		reRes      = regexp.MustCompile(`(\d{3,4}x\d{3,4}|\d{3,4}X\d{3,4}|\d{3,4}х\d{3,4}|\d{3,4}Х\d{3,4})`)
		resolution string
	)
	if reRes.MatchString(str) == true {
		resolution = reRes.FindString(str)
	}
	return resolution
}

func (t *Topic) getCountry() string {
	var (
		reCountry = regexp.MustCompile(`<span style="font-weight: bold">Производство:\s*<\/span>(.+?)<`)
		country   string
	)
	if reCountry.Match(t.Body) == true {
		country = string(reCountry.FindSubmatch(t.Body)[1])
		country = cleanStr(country)
	}
	return country
}

func (t *Topic) getGenre() string {
	var (
		reGenre = regexp.MustCompile(`<span style="font-weight: bold">Жанр:\s*<\/span>(.+?)<`)
		genre   string
	)
	if reGenre.Match(t.Body) == true {
		genre = string(reGenre.FindSubmatch(t.Body)[1])
		genre = strings.ToLower(cleanStr(genre))
		genre = strings.Trim(genre, ".")
	}
	return genre
}

func (t *Topic) getDirector() string {
	var (
		reDirector = regexp.MustCompile(`<span style="font-weight: bold">Режиссер:\s*<\/span>(.+?)<`)
		director   string
	)
	if reDirector.Match(t.Body) == true {
		director = string(reDirector.FindSubmatch(t.Body)[1])
		director = cleanStr(director)
		director = strings.Trim(director, ".")
	}
	return director
}

func (t *Topic) getProducer() string {
	var (
		reProducer = regexp.MustCompile(`<span style="font-weight: bold">Продюсер:\s*<\/span>(.+?)<`)
		producer   string
	)
	if reProducer.Match(t.Body) == true {
		producer = string(reProducer.FindSubmatch(t.Body)[1])
		producer = cleanStr(producer)
		producer = strings.Trim(producer, ".")
	}
	return producer
}

func (t *Topic) getActors() string {
	var (
		reActors = regexp.MustCompile(`<span style="font-weight: bold">Актеры:\s*<\/span>(.+?)<`)
		actors   string
	)
	if reActors.Match(t.Body) == true {
		actors = string(reActors.FindSubmatch(t.Body)[1])
		actors = cleanStr(actors)
		actors = strings.Trim(actors, ".")
	}
	return actors
}

func (t *Topic) getDescription() string {
	var (
		reDescription = regexp.MustCompile(`<span style="font-weight: bold">(?:Описание фильма|Описание):\s*<\/span>(.+?)<`)
		description   string
	)
	if reDescription.Match(t.Body) == true {
		description = string(reDescription.FindSubmatch(t.Body)[1])
		description = cleanStr(description)
	}
	return description
}

func (t *Topic) getAge() string {
	var (
		reAge = regexp.MustCompile(`<span style="font-weight: bold">Возраст:\s*<\/span>(.+?)<`)
		age   string
	)
	if reAge.Match(t.Body) == true {
		age = string(reAge.FindSubmatch(t.Body)[1])
		age = cleanStr(age)
	}
	return age
}

func (t *Topic) getReleaseDate() string {
	var (
		reReleaseDate = regexp.MustCompile(`<span style="font-weight: bold">Дата мировой премьеры:\s*<\/span>(.+?)<`)
		releaseDate   string
	)
	if reReleaseDate.Match(t.Body) == true {
		releaseDate = string(reReleaseDate.FindSubmatch(t.Body)[1])
		releaseDate = cleanStr(releaseDate)
		releaseDate = replaceDate(releaseDate)
	}
	return releaseDate
}

func (t *Topic) getRussianDate() string {
	var (
		reRussianDate = regexp.MustCompile(`<span style="font-weight: bold">(?:Дата премьеры в России|Дата Российской премьеры|Дата российской премьеры):\s*<\/span>(.+?)<`)
		russianDate   string
	)
	if reRussianDate.Match(t.Body) == true {
		russianDate = string(reRussianDate.FindSubmatch(t.Body)[1])
		russianDate = cleanStr(russianDate)
		russianDate = replaceDate(russianDate)
	}
	return russianDate
}

func (t *Topic) getDuration() string {
	var (
		reDuration = regexp.MustCompile(`<span style="font-weight: bold">Продолжительность:\s*<\/span>(.+?)<`)
		duration   string
	)
	if reDuration.Match(t.Body) == true {
		duration = string(reDuration.FindSubmatch(t.Body)[1])
		duration = cleanStr(duration)
	} else {
		reDuration = regexp.MustCompile(`\sПродолжительность\s+?: (\d{1,2}) ч\. (\d{1,2}) м\.`)
		if reDuration.Match(t.Body) == true {
			submatch := reDuration.FindSubmatch(t.Body)
			hour := string(submatch[1])
			minute := string(submatch[2])
			if len(hour) == 1 {
				hour = "0" + hour
			}
			if len(minute) == 1 {
				minute = "0" + minute
			}
			duration = hour + ":" + minute + ":00"
		}
	}
	if len(duration) < 5 {
		duration = ""
	}
	return duration
}

func (t *Topic) getQuality() string {
	var (
		reQuality = regexp.MustCompile(`<span style="font-weight: bold">(?:Качество видео|Качество):\s*<\/span>(.+?)<`)
		quality   string
	)
	if reQuality.Match(t.Body) == true {
		quality = string(reQuality.FindSubmatch(t.Body)[1])
		quality = cleanStr(quality)
	}
	return quality
}

func (t *Topic) getTranslation() string {
	var (
		reTranslation = regexp.MustCompile(`<span style="font-weight: bold">Перевод:\s*<\/span>(.+?)<`)
		translation   string
	)
	translation = "Не требуется"
	if reTranslation.Match(t.Body) == true {
		translation = string(reTranslation.FindSubmatch(t.Body)[1])
		translation = cleanStr(translation)
	}
	return translation
}

func (t *Topic) getSubtitlesType() string {
	var (
		reSubtitlesType = regexp.MustCompile(`<span style="font-weight: bold">Вид субтитров:\s*<\/span>(.+?)<`)
		subtitlesType   string
	)
	if reSubtitlesType.Match(t.Body) == true {
		subtitlesType = string(reSubtitlesType.FindSubmatch(t.Body)[1])
		subtitlesType = cleanStr(subtitlesType)
	}
	return subtitlesType
}

func (t *Topic) getSubtitles() string {
	var (
		reSubtitles = regexp.MustCompile(`<span style="font-weight: bold">Субтитры:\s*<\/span>(.+?)<`)
		subtitles   string
	)
	if reSubtitles.Match(t.Body) == true {
		subtitles = string(reSubtitles.FindSubmatch(t.Body)[1])
		subtitles = cleanStr(subtitles)
	}
	return subtitles
}

func (t *Topic) getVideo() string {
	var (
		reVideo = regexp.MustCompile(`<span style="font-weight: bold">Видео:\s*<\/span>(.+?)<`)
		video   string
	)
	if reVideo.Match(t.Body) == true {
		video = string(reVideo.FindSubmatch(t.Body)[1])
		video = cleanStr(video)
	}
	return video
}

func (t *Topic) getAudio1() string {
	var (
		reAudio = regexp.MustCompile(`<span style="font-weight: bold">(?:Аудио\s?:\s*|Аудио\s?.?1.?:\s*)<\/span>(.+?)<`)
		audio   string
	)
	if reAudio.Match(t.Body) == true {
		audio = string(reAudio.FindSubmatch(t.Body)[1])
		audio = cleanStr(audio)
	}
	return audio
}

func (t *Topic) getAudio2() string {
	var (
		reAudio = regexp.MustCompile(`<span style="font-weight: bold">Аудио\s?.?2.?:\s*<\/span>(.+?)<`)
		audio   string
	)
	if reAudio.Match(t.Body) == true {
		audio = string(reAudio.FindSubmatch(t.Body)[1])
		audio = cleanStr(audio)
	}
	return audio
}

func (t *Topic) getAudio3() string {
	var (
		reAudio = regexp.MustCompile(`<span style="font-weight: bold">Аудио\s?.?3.?:\s*<\/span>(.+?)<`)
		audio   string
	)
	if reAudio.Match(t.Body) == true {
		audio = string(reAudio.FindSubmatch(t.Body)[1])
		audio = cleanStr(audio)
	}
	return audio
}
<|endoftext|>