<commit_before>package gorm

import (
	"errors"
	"fmt"
	"reflect"
	"strings"
)

type Association struct {
	Scope  *Scope
	Column string
	Error  error
	Field  *Field
}

func (association *Association) setErr(err error) *Association {
	if err != nil {
		association.Error = err
	}
	return association
}

func (association *Association) Find(value interface{}) *Association {
	association.Scope.related(value, association.Column)
	return association.setErr(association.Scope.db.Error)
}

func (association *Association) saveAssociations(values ...interface{}) *Association {
	scope := association.Scope
	field := association.Field
	relationship := association.Field.Relationship

	saveAssociation := func(reflectValue reflect.Value) {
		// value has to been pointer
		if reflectValue.Kind() != reflect.Ptr {
			reflectPtr := reflect.New(reflectValue.Type())
			reflectPtr.Elem().Set(reflectValue)
			reflectValue = reflectPtr
		}

		// value has to been saved
		if scope.New(reflectValue.Interface()).PrimaryKeyZero() {
			scope.NewDB().Save(reflectValue.Interface())
		}

		// Assign Fields
		fieldType := field.Field.Type()
		if reflectValue.Type().AssignableTo(fieldType) {
			field.Set(reflectValue)
		} else if reflectValue.Type().Elem().AssignableTo(fieldType) {
			field.Set(reflectValue.Elem())
		} else if fieldType.Kind() == reflect.Slice {
			if reflectValue.Type().AssignableTo(fieldType.Elem()) {
				field.Set(reflect.Append(field.Field, reflectValue))
			} else if reflectValue.Type().Elem().AssignableTo(fieldType.Elem()) {
				field.Set(reflect.Append(field.Field, reflectValue.Elem()))
			}
		}

		if relationship.Kind == "many_to_many" {
			association.setErr(relationship.JoinTableHandler.Add(relationship.JoinTableHandler, scope.NewDB(), scope.Value, reflectValue.Interface()))
		} else {
			association.setErr(scope.NewDB().Select(field.Name).Save(scope.Value).Error)
		}
	}

	for _, value := range values {
		reflectValue := reflect.ValueOf(value)
		indirectReflectValue := reflect.Indirect(reflectValue)
		if indirectReflectValue.Kind() == reflect.Struct {
			saveAssociation(reflectValue)
		} else if indirectReflectValue.Kind() == reflect.Slice {
			for i := 0; i < indirectReflectValue.Len(); i++ {
				saveAssociation(indirectReflectValue.Index(i))
			}
		} else {
			association.setErr(errors.New("invalid value type"))
		}
	}
	return association
}

func (association *Association) Append(values ...interface{}) *Association {
	if relationship := association.Field.Relationship; relationship.Kind == "has_one" {
		return association.Replace(values...)
	}
	return association.saveAssociations(values...)
}

func (association *Association) Replace(values ...interface{}) *Association {
	var (
		relationship = association.Field.Relationship
		scope        = association.Scope
		field        = association.Field.Field
		newDB        = scope.NewDB()
	)

	// Append new values
	association.Field.Set(reflect.Zero(association.Field.Field.Type()))
	association.saveAssociations(values...)

	// Belongs To
	if relationship.Kind == "belongs_to" {
		// Set foreign key to be null only when clearing value
		if len(values) == 0 {
			// Set foreign key to be nil
			var foreignKeyMap = map[string]interface{}{}
			for _, foreignKey := range relationship.ForeignDBNames {
				foreignKeyMap[foreignKey] = nil
			}
			association.setErr(newDB.Model(scope.Value).UpdateColumn(foreignKeyMap).Error)
		}
	} else {
		// Relations
		var foreignKeyMap = map[string]interface{}{}
		for idx, foreignKey := range relationship.ForeignDBNames {
			foreignKeyMap[foreignKey] = nil
			if field, ok := scope.FieldByName(relationship.AssociationForeignFieldNames[idx]); ok {
				newDB = newDB.Where(fmt.Sprintf("%v = ?", scope.Quote(foreignKey)), field.Field.Interface())
			}
		}

		if relationship.PolymorphicDBName != "" {
			newDB = newDB.Where(fmt.Sprintf("%v = ?", scope.Quote(relationship.PolymorphicDBName)), scope.TableName())
		}

		// Relations except new created
		if len(values) > 0 {
			var newPrimaryKeys [][]interface{}
			var associationForeignFieldNames []string

			if relationship.Kind == "many2many" {
				// If many to many relations, get it from foreign key
				associationForeignFieldNames = relationship.AssociationForeignFieldNames
			} else {
				// If other relations, get real primary keys
				for _, field := range scope.New(reflect.New(field.Type()).Interface()).Fields() {
					if field.IsPrimaryKey {
						associationForeignFieldNames = append(associationForeignFieldNames, field.Name)
					}
				}
			}

			newPrimaryKeys = association.getPrimaryKeys(associationForeignFieldNames, field.Interface())
			sql := fmt.Sprintf("%v NOT IN (%v)", toQueryCondition(scope, relationship.AssociationForeignDBNames), toQueryMarks(newPrimaryKeys))
			newDB = newDB.Where(sql, toQueryValues(newPrimaryKeys)...)
		}

		if relationship.Kind == "many_to_many" {
			association.setErr(relationship.JoinTableHandler.Delete(relationship.JoinTableHandler, newDB, relationship))
		} else if relationship.Kind == "has_one" || relationship.Kind == "has_many" {
			fieldValue := reflect.New(association.Field.Field.Type()).Interface()
			association.setErr(newDB.Model(fieldValue).UpdateColumn(foreignKeyMap).Error)
		}
	}
	return association
}

func (association *Association) Delete(values ...interface{}) *Association {
	var (
		relationship = association.Field.Relationship
		scope        = association.Scope
		field        = association.Field.Field
		newDB        = scope.NewDB()
	)

	if len(values) == 0 {
		return association
	}

	var deletingResourcePrimaryFieldNames, deletingResourcePrimaryDBNames []string
	for _, field := range scope.New(reflect.New(field.Type()).Interface()).Fields() {
		if field.IsPrimaryKey {
			deletingResourcePrimaryFieldNames = append(deletingResourcePrimaryFieldNames, field.Name)
			deletingResourcePrimaryDBNames = append(deletingResourcePrimaryDBNames, field.DBName)
		}
	}

	deletingPrimaryKeys := association.getPrimaryKeys(deletingResourcePrimaryFieldNames, values...)

	if relationship.Kind == "many_to_many" {
		// source value's foreign keys
		for idx, foreignKey := range relationship.ForeignDBNames {
			if field, ok := scope.FieldByName(relationship.ForeignFieldNames[idx]); ok {
				newDB = newDB.Where(fmt.Sprintf("%v = ?", scope.Quote(foreignKey)), field.Field.Interface())
			}
		}

		// association value's foreign keys
		deletingPrimaryKeys := association.getPrimaryKeys(relationship.AssociationForeignFieldNames, values...)
		sql := fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, relationship.AssociationForeignDBNames), toQueryMarks(deletingPrimaryKeys))
		newDB = newDB.Where(sql, toQueryValues(deletingPrimaryKeys)...)

		association.setErr(relationship.JoinTableHandler.Delete(relationship.JoinTableHandler, newDB, relationship))
	} else {
		var foreignKeyMap = map[string]interface{}{}
		for _, foreignKey := range relationship.ForeignDBNames {
			foreignKeyMap[foreignKey] = nil
		}

		if relationship.Kind == "belongs_to" {
			// find with deleting relation's foreign keys
			primaryKeys := association.getPrimaryKeys(relationship.AssociationForeignFieldNames, values...)
			newDB = newDB.Where(
				fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, relationship.ForeignDBNames), toQueryMarks(primaryKeys)),
				toQueryValues(primaryKeys)...,
			)

			// set foreign key to be null
			modelValue := reflect.New(scope.GetModelStruct().ModelType).Interface()
			if results := newDB.Model(modelValue).UpdateColumn(foreignKeyMap); results.Error == nil {
				if results.RowsAffected > 0 {
					scope.updatedAttrsWithValues(foreignKeyMap, false)
				}
			} else {
				association.setErr(results.Error)
			}
		} else if relationship.Kind == "has_one" || relationship.Kind == "has_many" {
			// find all relations
			primaryKeys := association.getPrimaryKeys(relationship.AssociationForeignFieldNames, scope.Value)
			newDB = newDB.Where(
				fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, relationship.ForeignDBNames), toQueryMarks(primaryKeys)),
				toQueryValues(primaryKeys)...,
			)

			// only include those deleting relations
			newDB = newDB.Where(
				fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, deletingResourcePrimaryDBNames), toQueryMarks(deletingPrimaryKeys)),
				toQueryValues(deletingPrimaryKeys)...,
			)

			// set matched relation's foreign key to be null
			fieldValue := reflect.New(association.Field.Field.Type()).Interface()
			association.setErr(newDB.Model(fieldValue).UpdateColumn(foreignKeyMap).Error)
		}
	}

	// Remove deleted records from field
	if association.Error == nil {
		if association.Field.Field.Kind() == reflect.Slice {
			leftValues := reflect.Zero(association.Field.Field.Type())

			for i := 0; i < association.Field.Field.Len(); i++ {
				reflectValue := association.Field.Field.Index(i)
				primaryKey := association.getPrimaryKeys(deletingResourcePrimaryFieldNames, reflectValue.Interface())[0]
				var included = false
				for _, pk := range deletingPrimaryKeys {
					if equalAsString(primaryKey, pk) {
						included = true
					}
				}
				if !included {
					leftValues = reflect.Append(leftValues, reflectValue)
				}
			}

			association.Field.Set(leftValues)
		} else if association.Field.Field.Kind() == reflect.Struct {
			primaryKey := association.getPrimaryKeys(deletingResourcePrimaryFieldNames, association.Field.Field.Interface())[0]
			for _, pk := range deletingPrimaryKeys {
				if equalAsString(primaryKey, pk) {
					association.Field.Set(reflect.Zero(association.Field.Field.Type()))
					break
				}
			}
		}
	}

	return association
}

func (association *Association) Clear() *Association {
	return association.Replace()
}

func (association *Association) Count() int {
	count := -1
	relationship := association.Field.Relationship
	scope := association.Scope
	newScope := scope.New(association.Field.Field.Interface())

	if relationship.Kind == "many_to_many" {
		relationship.JoinTableHandler.JoinWith(relationship.JoinTableHandler, scope.NewDB(), association.Scope.Value).Table(newScope.TableName()).Count(&count)
	} else if relationship.Kind == "has_many" || relationship.Kind == "has_one" {
		query := scope.DB()
		for idx, foreignKey := range relationship.ForeignDBNames {
			if field, ok := scope.FieldByName(relationship.AssociationForeignDBNames[idx]); ok {
				query = query.Where(fmt.Sprintf("%v.%v = ?", newScope.QuotedTableName(), scope.Quote(foreignKey)),
					field.Field.Interface())
			}
		}

		if relationship.PolymorphicType != "" {
			query = query.Where(fmt.Sprintf("%v.%v = ?", newScope.QuotedTableName(), newScope.Quote(relationship.PolymorphicDBName)), scope.TableName())
		}
		query.Table(newScope.TableName()).Count(&count)
	} else if relationship.Kind == "belongs_to" {
		query := scope.DB()
		for idx, foreignKey := range relationship.ForeignDBNames {
			if field, ok := scope.FieldByName(relationship.AssociationForeignDBNames[idx]); ok {
				query = query.Where(fmt.Sprintf("%v.%v = ?", newScope.QuotedTableName(), scope.Quote(foreignKey)),
					field.Field.Interface())
			}
		}
		query.Table(newScope.TableName()).Count(&count)
	}

	return count
}

func (association *Association) getPrimaryKeys(columns []string, values ...interface{}) (results [][]interface{}) {
	scope := association.Scope

	for _, value := range values {
		reflectValue := reflect.Indirect(reflect.ValueOf(value))
		if reflectValue.Kind() == reflect.Slice {
			for i := 0; i < reflectValue.Len(); i++ {
				primaryKeys := []interface{}{}
				newScope := scope.New(reflectValue.Index(i).Interface())
				for _, column := range columns {
					if field, ok := newScope.FieldByName(column); ok {
						primaryKeys = append(primaryKeys, field.Field.Interface())
					} else {
						primaryKeys = append(primaryKeys, "")
					}
				}
				results = append(results, primaryKeys)
			}
		} else if reflectValue.Kind() == reflect.Struct {
			newScope := scope.New(value)
			var primaryKeys []interface{}
			for _, column := range columns {
				if field, ok := newScope.FieldByName(column); ok {
					primaryKeys = append(primaryKeys, field.Field.Interface())
				} else {
					primaryKeys = append(primaryKeys, "")
				}
			}

			results = append(results, primaryKeys)
		}
	}

	return
}

func toQueryMarks(primaryValues [][]interface{}) string {
	var results []string

	for _, primaryValue := range primaryValues {
		var marks []string
		for _, _ = range primaryValue {
			marks = append(marks, "?")
		}

		if len(marks) > 1 {
			results = append(results, fmt.Sprintf("(%v)", strings.Join(marks, ",")))
		} else {
			results = append(results, strings.Join(marks, ""))
		}
	}
	return strings.Join(results, ",")
}

func toQueryCondition(scope *Scope, columns []string) string {
	var newColumns []string
	for _, column := range columns {
		newColumns = append(newColumns, scope.Quote(column))
	}

	if len(columns) > 1 {
		return fmt.Sprintf("(%v)", strings.Join(newColumns, ","))
	} else {
		return strings.Join(columns, ",")
	}
}

func toQueryValues(primaryValues [][]interface{}) (values []interface{}) {
	for _, primaryValue := range primaryValues {
		for _, value := range primaryValue {
			values = append(values, value)
		}
	}
	return values
}
<commit_msg>Remove unnecessary saving SQL when Append new association<commit_after>package gorm

import (
	"errors"
	"fmt"
	"reflect"
	"strings"
)

type Association struct {
	Scope  *Scope
	Column string
	Error  error
	Field  *Field
}

func (association *Association) setErr(err error) *Association {
	if err != nil {
		association.Error = err
	}
	return association
}

func (association *Association) Find(value interface{}) *Association {
	association.Scope.related(value, association.Column)
	return association.setErr(association.Scope.db.Error)
}

func (association *Association) saveAssociations(values ...interface{}) *Association {
	scope := association.Scope
	field := association.Field
	relationship := association.Field.Relationship

	saveAssociation := func(reflectValue reflect.Value) {
		// value has to been pointer
		if reflectValue.Kind() != reflect.Ptr {
			reflectPtr := reflect.New(reflectValue.Type())
			reflectPtr.Elem().Set(reflectValue)
			reflectValue = reflectPtr
		}

		// value has to been saved for many2many
		if relationship.Kind == "many_to_many" {
			if scope.New(reflectValue.Interface()).PrimaryKeyZero() {
				scope.NewDB().Save(reflectValue.Interface())
			}
		}

		// Assign Fields
		var fieldType = field.Field.Type()
		var setFieldBackToValue, setSliceFieldBackToValue bool
		if reflectValue.Type().AssignableTo(fieldType) {
			field.Set(reflectValue)
		} else if reflectValue.Type().Elem().AssignableTo(fieldType) {
			// if field's type is struct, then need to set value back to argument after save
			setFieldBackToValue = true
			field.Set(reflectValue.Elem())
		} else if fieldType.Kind() == reflect.Slice {
			if reflectValue.Type().AssignableTo(fieldType.Elem()) {
				field.Set(reflect.Append(field.Field, reflectValue))
			} else if reflectValue.Type().Elem().AssignableTo(fieldType.Elem()) {
				// if field's type is slice of struct, then need to set value back to argument after save
				setSliceFieldBackToValue = true
				field.Set(reflect.Append(field.Field, reflectValue.Elem()))
			}
		}

		if relationship.Kind == "many_to_many" {
			association.setErr(relationship.JoinTableHandler.Add(relationship.JoinTableHandler, scope.NewDB(), scope.Value, reflectValue.Interface()))
		} else {
			association.setErr(scope.NewDB().Select(field.Name).Save(scope.Value).Error)

			if setFieldBackToValue {
				reflectValue.Elem().Set(field.Field)
			} else if setSliceFieldBackToValue {
				reflectValue.Elem().Set(field.Field.Index(field.Field.Len() - 1))
			}
		}
	}

	for _, value := range values {
		reflectValue := reflect.ValueOf(value)
		indirectReflectValue := reflect.Indirect(reflectValue)
		if indirectReflectValue.Kind() == reflect.Struct {
			saveAssociation(reflectValue)
		} else if indirectReflectValue.Kind() == reflect.Slice {
			for i := 0; i < indirectReflectValue.Len(); i++ {
				saveAssociation(indirectReflectValue.Index(i))
			}
		} else {
			association.setErr(errors.New("invalid value type"))
		}
	}
	return association
}

func (association *Association) Append(values ...interface{}) *Association {
	if relationship := association.Field.Relationship; relationship.Kind == "has_one" {
		return association.Replace(values...)
	}
	return association.saveAssociations(values...)
}

func (association *Association) Replace(values ...interface{}) *Association {
	var (
		relationship = association.Field.Relationship
		scope        = association.Scope
		field        = association.Field.Field
		newDB        = scope.NewDB()
	)

	// Append new values
	association.Field.Set(reflect.Zero(association.Field.Field.Type()))
	association.saveAssociations(values...)

	// Belongs To
	if relationship.Kind == "belongs_to" {
		// Set foreign key to be null only when clearing value
		if len(values) == 0 {
			// Set foreign key to be nil
			var foreignKeyMap = map[string]interface{}{}
			for _, foreignKey := range relationship.ForeignDBNames {
				foreignKeyMap[foreignKey] = nil
			}
			association.setErr(newDB.Model(scope.Value).UpdateColumn(foreignKeyMap).Error)
		}
	} else {
		// Relations
		var foreignKeyMap = map[string]interface{}{}
		for idx, foreignKey := range relationship.ForeignDBNames {
			foreignKeyMap[foreignKey] = nil
			if field, ok := scope.FieldByName(relationship.AssociationForeignFieldNames[idx]); ok {
				newDB = newDB.Where(fmt.Sprintf("%v = ?", scope.Quote(foreignKey)), field.Field.Interface())
			}
		}

		if relationship.PolymorphicDBName != "" {
			newDB = newDB.Where(fmt.Sprintf("%v = ?", scope.Quote(relationship.PolymorphicDBName)), scope.TableName())
		}

		// Relations except new created
		if len(values) > 0 {
			var newPrimaryKeys [][]interface{}
			var associationForeignFieldNames []string

			if relationship.Kind == "many2many" {
				// If many to many relations, get it from foreign key
				associationForeignFieldNames = relationship.AssociationForeignFieldNames
			} else {
				// If other relations, get real primary keys
				for _, field := range scope.New(reflect.New(field.Type()).Interface()).Fields() {
					if field.IsPrimaryKey {
						associationForeignFieldNames = append(associationForeignFieldNames, field.Name)
					}
				}
			}

			newPrimaryKeys = association.getPrimaryKeys(associationForeignFieldNames, field.Interface())
			sql := fmt.Sprintf("%v NOT IN (%v)", toQueryCondition(scope, relationship.AssociationForeignDBNames), toQueryMarks(newPrimaryKeys))
			newDB = newDB.Where(sql, toQueryValues(newPrimaryKeys)...)
		}

		if relationship.Kind == "many_to_many" {
			association.setErr(relationship.JoinTableHandler.Delete(relationship.JoinTableHandler, newDB, relationship))
		} else if relationship.Kind == "has_one" || relationship.Kind == "has_many" {
			fieldValue := reflect.New(association.Field.Field.Type()).Interface()
			association.setErr(newDB.Model(fieldValue).UpdateColumn(foreignKeyMap).Error)
		}
	}
	return association
}

func (association *Association) Delete(values ...interface{}) *Association {
	var (
		relationship = association.Field.Relationship
		scope        = association.Scope
		field        = association.Field.Field
		newDB        = scope.NewDB()
	)

	if len(values) == 0 {
		return association
	}

	var deletingResourcePrimaryFieldNames, deletingResourcePrimaryDBNames []string
	for _, field := range scope.New(reflect.New(field.Type()).Interface()).Fields() {
		if field.IsPrimaryKey {
			deletingResourcePrimaryFieldNames = append(deletingResourcePrimaryFieldNames, field.Name)
			deletingResourcePrimaryDBNames = append(deletingResourcePrimaryDBNames, field.DBName)
		}
	}

	deletingPrimaryKeys := association.getPrimaryKeys(deletingResourcePrimaryFieldNames, values...)

	if relationship.Kind == "many_to_many" {
		// source value's foreign keys
		for idx, foreignKey := range relationship.ForeignDBNames {
			if field, ok := scope.FieldByName(relationship.ForeignFieldNames[idx]); ok {
				newDB = newDB.Where(fmt.Sprintf("%v = ?", scope.Quote(foreignKey)), field.Field.Interface())
			}
		}

		// association value's foreign keys
		deletingPrimaryKeys := association.getPrimaryKeys(relationship.AssociationForeignFieldNames, values...)
		sql := fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, relationship.AssociationForeignDBNames), toQueryMarks(deletingPrimaryKeys))
		newDB = newDB.Where(sql, toQueryValues(deletingPrimaryKeys)...)

		association.setErr(relationship.JoinTableHandler.Delete(relationship.JoinTableHandler, newDB, relationship))
	} else {
		var foreignKeyMap = map[string]interface{}{}
		for _, foreignKey := range relationship.ForeignDBNames {
			foreignKeyMap[foreignKey] = nil
		}

		if relationship.Kind == "belongs_to" {
			// find with deleting relation's foreign keys
			primaryKeys := association.getPrimaryKeys(relationship.AssociationForeignFieldNames, values...)
			newDB = newDB.Where(
				fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, relationship.ForeignDBNames), toQueryMarks(primaryKeys)),
				toQueryValues(primaryKeys)...,
			)

			// set foreign key to be null
			modelValue := reflect.New(scope.GetModelStruct().ModelType).Interface()
			if results := newDB.Model(modelValue).UpdateColumn(foreignKeyMap); results.Error == nil {
				if results.RowsAffected > 0 {
					scope.updatedAttrsWithValues(foreignKeyMap, false)
				}
			} else {
				association.setErr(results.Error)
			}
		} else if relationship.Kind == "has_one" || relationship.Kind == "has_many" {
			// find all relations
			primaryKeys := association.getPrimaryKeys(relationship.AssociationForeignFieldNames, scope.Value)
			newDB = newDB.Where(
				fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, relationship.ForeignDBNames), toQueryMarks(primaryKeys)),
				toQueryValues(primaryKeys)...,
			)

			// only include those deleting relations
			newDB = newDB.Where(
				fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, deletingResourcePrimaryDBNames), toQueryMarks(deletingPrimaryKeys)),
				toQueryValues(deletingPrimaryKeys)...,
			)

			// set matched relation's foreign key to be null
			fieldValue := reflect.New(association.Field.Field.Type()).Interface()
			association.setErr(newDB.Model(fieldValue).UpdateColumn(foreignKeyMap).Error)
		}
	}

	// Remove deleted records from field
	if association.Error == nil {
		if association.Field.Field.Kind() == reflect.Slice {
			leftValues := reflect.Zero(association.Field.Field.Type())

			for i := 0; i < association.Field.Field.Len(); i++ {
				reflectValue := association.Field.Field.Index(i)
				primaryKey := association.getPrimaryKeys(deletingResourcePrimaryFieldNames, reflectValue.Interface())[0]
				var included = false
				for _, pk := range deletingPrimaryKeys {
					if equalAsString(primaryKey, pk) {
						included = true
					}
				}
				if !included {
					leftValues = reflect.Append(leftValues, reflectValue)
				}
			}

			association.Field.Set(leftValues)
		} else if association.Field.Field.Kind() == reflect.Struct {
			primaryKey := association.getPrimaryKeys(deletingResourcePrimaryFieldNames, association.Field.Field.Interface())[0]
			for _, pk := range deletingPrimaryKeys {
				if equalAsString(primaryKey, pk) {
					association.Field.Set(reflect.Zero(association.Field.Field.Type()))
					break
				}
			}
		}
	}

	return association
}

func (association *Association) Clear() *Association {
	return association.Replace()
}

func (association *Association) Count() int {
	count := -1
	relationship := association.Field.Relationship
	scope := association.Scope
	newScope := scope.New(association.Field.Field.Interface())

	if relationship.Kind == "many_to_many" {
		relationship.JoinTableHandler.JoinWith(relationship.JoinTableHandler, scope.NewDB(), association.Scope.Value).Table(newScope.TableName()).Count(&count)
	} else if relationship.Kind == "has_many" || relationship.Kind == "has_one" {
		query := scope.DB()
		for idx, foreignKey := range relationship.ForeignDBNames {
			if field, ok := scope.FieldByName(relationship.AssociationForeignDBNames[idx]); ok {
				query = query.Where(fmt.Sprintf("%v.%v = ?", newScope.QuotedTableName(), scope.Quote(foreignKey)),
					field.Field.Interface())
			}
		}

		if relationship.PolymorphicType != "" {
			query = query.Where(fmt.Sprintf("%v.%v = ?", newScope.QuotedTableName(), newScope.Quote(relationship.PolymorphicDBName)), scope.TableName())
		}
		query.Table(newScope.TableName()).Count(&count)
	} else if relationship.Kind == "belongs_to" {
		query := scope.DB()
		for idx, foreignKey := range relationship.ForeignDBNames {
			if field, ok := scope.FieldByName(relationship.AssociationForeignDBNames[idx]); ok {
				query = query.Where(fmt.Sprintf("%v.%v = ?", newScope.QuotedTableName(), scope.Quote(foreignKey)),
					field.Field.Interface())
			}
		}
		query.Table(newScope.TableName()).Count(&count)
	}

	return count
}

func (association *Association) getPrimaryKeys(columns []string, values ...interface{}) (results [][]interface{}) {
	scope := association.Scope

	for _, value := range values {
		reflectValue := reflect.Indirect(reflect.ValueOf(value))
		if reflectValue.Kind() == reflect.Slice {
			for i := 0; i < reflectValue.Len(); i++ {
				primaryKeys := []interface{}{}
				newScope := scope.New(reflectValue.Index(i).Interface())
				for _, column := range columns {
					if field, ok := newScope.FieldByName(column); ok {
						primaryKeys = append(primaryKeys, field.Field.Interface())
					} else {
						primaryKeys = append(primaryKeys, "")
					}
				}
				results = append(results, primaryKeys)
			}
		} else if reflectValue.Kind() == reflect.Struct {
			newScope := scope.New(value)
			var primaryKeys []interface{}
			for _, column := range columns {
				if field, ok := newScope.FieldByName(column); ok {
					primaryKeys = append(primaryKeys, field.Field.Interface())
				} else {
					primaryKeys = append(primaryKeys, "")
				}
			}

			results = append(results, primaryKeys)
		}
	}

	return
}

func toQueryMarks(primaryValues [][]interface{}) string {
	var results []string

	for _, primaryValue := range primaryValues {
		var marks []string
		for _, _ = range primaryValue {
			marks = append(marks, "?")
		}

		if len(marks) > 1 {
			results = append(results, fmt.Sprintf("(%v)", strings.Join(marks, ",")))
		} else {
			results = append(results, strings.Join(marks, ""))
		}
	}
	return strings.Join(results, ",")
}

func toQueryCondition(scope *Scope, columns []string) string {
	var newColumns []string
	for _, column := range columns {
		newColumns = append(newColumns, scope.Quote(column))
	}

	if len(columns) > 1 {
		return fmt.Sprintf("(%v)", strings.Join(newColumns, ","))
	} else {
		return strings.Join(columns, ",")
	}
}

func toQueryValues(primaryValues [][]interface{}) (values []interface{}) {
	for _, primaryValue := range primaryValues {
		for _, value := range primaryValue {
			values = append(values, value)
		}
	}
	return values
}
<|endoftext|>


<commit_before>package qlog

import (
	"time"

	"github.com/francoispqt/gojay"

	"github.com/lucas-clemente/quic-go/internal/protocol"
)

type topLevel struct {
	traces traces
}

func (topLevel) IsNil() bool { return false }
func (l topLevel) MarshalJSONObject(enc *gojay.Encoder) {
	enc.StringKey("qlog_version", "draft-02-wip")
	enc.StringKeyOmitEmpty("title", "quic-go qlog")
	enc.ArrayKey("traces", l.traces)
}

type vantagePoint struct {
	Name string
	Type protocol.Perspective
}

func (p vantagePoint) IsNil() bool { return false }
func (p vantagePoint) MarshalJSONObject(enc *gojay.Encoder) {
	enc.StringKeyOmitEmpty("name", p.Name)
	switch p.Type {
	case protocol.PerspectiveClient:
		enc.StringKey("type", "client")
	case protocol.PerspectiveServer:
		enc.StringKey("type", "server")
	}
}

type commonFields struct {
	ODCID         connectionID
	GroupID       connectionID
	ProtocolType  string
	ReferenceTime time.Time
}

func (f commonFields) MarshalJSONObject(enc *gojay.Encoder) {
	enc.StringKey("ODCID", f.ODCID.String())
	enc.StringKey("group_id", f.ODCID.String())
	enc.StringKeyOmitEmpty("protocol_type", f.ProtocolType)
	enc.Float64Key("reference_time", float64(f.ReferenceTime.UnixNano()/1e6))
}

func (f commonFields) IsNil() bool { return false }

type traces []trace

func (t traces) IsNil() bool { return t == nil }
func (t traces) MarshalJSONArray(enc *gojay.Encoder) {
	for _, tr := range t {
		enc.Object(tr)
	}
}

type trace struct {
	VantagePoint vantagePoint
	CommonFields commonFields
	EventFields  []string
	Events       events
}

func (trace) IsNil() bool { return false }
func (t trace) MarshalJSONObject(enc *gojay.Encoder) {
	enc.ObjectKey("vantage_point", t.VantagePoint)
	enc.ObjectKey("common_fields", t.CommonFields)
	enc.SliceStringKey("event_fields", t.EventFields)
	enc.ArrayKey("events", t.Events)
}
<commit_msg>fix precision of reference_time field in qlog<commit_after>package qlog

import (
	"time"

	"github.com/francoispqt/gojay"

	"github.com/lucas-clemente/quic-go/internal/protocol"
)

type topLevel struct {
	traces traces
}

func (topLevel) IsNil() bool { return false }
func (l topLevel) MarshalJSONObject(enc *gojay.Encoder) {
	enc.StringKey("qlog_version", "draft-02-wip")
	enc.StringKeyOmitEmpty("title", "quic-go qlog")
	enc.ArrayKey("traces", l.traces)
}

type vantagePoint struct {
	Name string
	Type protocol.Perspective
}

func (p vantagePoint) IsNil() bool { return false }
func (p vantagePoint) MarshalJSONObject(enc *gojay.Encoder) {
	enc.StringKeyOmitEmpty("name", p.Name)
	switch p.Type {
	case protocol.PerspectiveClient:
		enc.StringKey("type", "client")
	case protocol.PerspectiveServer:
		enc.StringKey("type", "server")
	}
}

type commonFields struct {
	ODCID         connectionID
	GroupID       connectionID
	ProtocolType  string
	ReferenceTime time.Time
}

func (f commonFields) MarshalJSONObject(enc *gojay.Encoder) {
	enc.StringKey("ODCID", f.ODCID.String())
	enc.StringKey("group_id", f.ODCID.String())
	enc.StringKeyOmitEmpty("protocol_type", f.ProtocolType)
	enc.Float64Key("reference_time", float64(f.ReferenceTime.UnixNano())/1e6)
}

func (f commonFields) IsNil() bool { return false }

type traces []trace

func (t traces) IsNil() bool { return t == nil }
func (t traces) MarshalJSONArray(enc *gojay.Encoder) {
	for _, tr := range t {
		enc.Object(tr)
	}
}

type trace struct {
	VantagePoint vantagePoint
	CommonFields commonFields
	EventFields  []string
	Events       events
}

func (trace) IsNil() bool { return false }
func (t trace) MarshalJSONObject(enc *gojay.Encoder) {
	enc.ObjectKey("vantage_point", t.VantagePoint)
	enc.ObjectKey("common_fields", t.CommonFields)
	enc.SliceStringKey("event_fields", t.EventFields)
	enc.ArrayKey("events", t.Events)
}
<|endoftext|>


<commit_before>package main

import (
	"io/ioutil"
	"math"
	"reflect"
	"testing"
	"time"

	"github.com/dustin/go-couchstore"
)

var testInput = []interface{}{}
var nextValue = "29"

var bigInput []byte

func init() {
	s := []interface{}{"31", "63", "foo", "17"}
	for i := range s {
		testInput = append(testInput, s[i])
	}

	var err error
	bigInput, err = ioutil.ReadFile("sample.json")
	if err != nil {
		panic("Couldn't read sample.json")
	}
}

func streamCollection(s []interface{}) chan ptrval {
	ch := make(chan ptrval)
	go func() {
		defer close(ch)
		t := time.Unix(1347255646, 418514126).UTC()
		for _, r := range s {
			t = t.Add(time.Second)
			ts := t.Format(time.RFC3339Nano)
			ch <- ptrval{couchstore.NewDocInfo(ts, 0), r, true}
		}
		t = t.Add(time.Second)
		ts := t.Format(time.RFC3339Nano)
		ch <- ptrval{couchstore.NewDocInfo(ts, 0), nextValue, false}
	}()
	return ch
}

func TestEmptyRateConversion(t *testing.T) {
	ch := make(chan ptrval)
	rch := convertTofloat64Rate(ch)
	close(ch)
	val, got := <-rch
	if got {
		t.Fatalf("Expected empty channel, got %v", val)
	}
}

func TestSingleRateConversion(t *testing.T) {
	ch := make(chan ptrval, 1)
	rch := convertTofloat64Rate(ch)
	ch <- ptrval{nil, &nextValue, true}
	close(ch)
	val, got := <-rch
	if got {
		t.Fatalf("Expected empty channel, got %v", val)
	}
}

func TestPairRateConversion(t *testing.T) {
	ch := make(chan ptrval, 2)
	rch := convertTofloat64Rate(ch)

	tm := time.Now().UTC()
	val1 := "20"
	ch <- ptrval{couchstore.NewDocInfo(tm.Format(time.RFC3339Nano), 0),
		val1, true}

	tm = tm.Add(5 * time.Second)
	val2 := "25"
	ch <- ptrval{couchstore.NewDocInfo(tm.Format(time.RFC3339Nano), 0),
		val2, false}

	close(ch)
	exp := 1.0
	val, got := <-rch
	if !got {
		t.Fatalf("Expected value, got empty channel")
	}
	if val != exp {
		t.Fatalf("Expected %v, got %v", exp, val)
	}
}

func TestReducers(t *testing.T) {
	tests := []struct {
		reducer string
		exp     interface{}
	}{
		{"any", "31"},
		{"count", 4},
		{"sum", float64(111)},
		{"sumsq", float64(5219)},
		{"max", float64(63)},
		{"min", float64(17)},
		{"avg", float64(37)},
		{"c_min", float64(-23)},
		{"c_avg", float64(7)},
		{"c_max", float64(32)},
		{"identity", testInput},
	}

	for _, test := range tests {
		got := reducers[test.reducer](streamCollection(testInput))
		if !reflect.DeepEqual(got, test.exp) {
			t.Errorf("Expected %v for %v, got %v",
				test.exp, test.reducer, got)
			t.Fail()
		}
	}
}

func TestEmptyReducers(t *testing.T) {
	emptyInput := []interface{}{}
	tests := []struct {
		reducer string
		exp     interface{}
	}{
		{"any", nil},
		{"count", 0},
		{"sum", 0.0},
		{"sumsq", 0.0},
		{"max", math.NaN()},
		{"min", math.NaN()},
		{"avg", math.NaN()},
		{"c_min", math.NaN()},
		{"c_avg", math.NaN()},
		{"c_max", math.NaN()},
		{"identity", emptyInput},
	}

	eq := func(a, b interface{}) bool {
		if !reflect.DeepEqual(a, b) {
			af, aok := a.(float64)
			bf, bok := b.(float64)
			return aok && bok && (math.IsNaN(af) == math.IsNaN(bf))
		}
		return true
	}

	for _, test := range tests {
		got := reducers[test.reducer](streamCollection(emptyInput))
		if !eq(got, test.exp) {
			t.Errorf("Expected %v for %v, got %v",
				test.exp, test.reducer, got)
			t.Fail()
		}
	}
}

func TestNilReducers(t *testing.T) {
	emptyInput := []interface{}{}
	tests := []struct {
		reducer string
		exp     interface{}
	}{
		{"any", nil},
		{"count", 0},
		{"sum", 0.0},
		{"sumsq", 0.0},
		{"max", math.NaN()},
		{"min", math.NaN()},
		{"avg", math.NaN()},
		{"c_min", math.NaN()},
		{"c_avg", math.NaN()},
		{"c_max", math.NaN()},
		{"identity", emptyInput},
	}

	eq := func(a, b interface{}) bool {
		if !reflect.DeepEqual(a, b) {
			af, aok := a.(float64)
			bf, bok := b.(float64)
			return aok && bok && (math.IsNaN(af) == math.IsNaN(bf))
		}
		return true
	}

	for _, test := range tests {
		got := reducers[test.reducer](streamCollection(emptyInput))
		if !eq(got, test.exp) {
			t.Errorf("Expected %v for %v, got %v",
				test.exp, test.reducer, got)
			t.Fail()
		}
	}
}

func TestPointers(t *testing.T) {
	docId := "2013-02-22T16:29:19.750264Z"
	di := couchstore.NewDocInfo(docId, 0)
	tests := []struct {
		pointer string
		exp     interface{}
	}{
		{"/kind", "Listing"},
		{"_id", docId},
	}

	for _, test := range tests {
		chans := make([]chan ptrval, 0, 1)
		chans = append(chans, make(chan ptrval))
		go processDoc(di, chans, bigInput, []string{test.pointer}, []string{}, []string{}, true)
		got := <-chans[0]
		if test.exp != *got.val {
			t.Errorf("Expected %v for %v, got %v",
				test.exp, test.pointer, *got.val)
		}
	}

}
<commit_msg>update TestPointers test case to work with interface{}<commit_after>package main

import (
	"io/ioutil"
	"math"
	"reflect"
	"testing"
	"time"

	"github.com/dustin/go-couchstore"
)

var testInput = []interface{}{}
var nextValue = "29"

var bigInput []byte

func init() {
	s := []interface{}{"31", "63", "foo", "17"}
	for i := range s {
		testInput = append(testInput, s[i])
	}

	var err error
	bigInput, err = ioutil.ReadFile("sample.json")
	if err != nil {
		panic("Couldn't read sample.json")
	}
}

func streamCollection(s []interface{}) chan ptrval {
	ch := make(chan ptrval)
	go func() {
		defer close(ch)
		t := time.Unix(1347255646, 418514126).UTC()
		for _, r := range s {
			t = t.Add(time.Second)
			ts := t.Format(time.RFC3339Nano)
			ch <- ptrval{couchstore.NewDocInfo(ts, 0), r, true}
		}
		t = t.Add(time.Second)
		ts := t.Format(time.RFC3339Nano)
		ch <- ptrval{couchstore.NewDocInfo(ts, 0), nextValue, false}
	}()
	return ch
}

func TestEmptyRateConversion(t *testing.T) {
	ch := make(chan ptrval)
	rch := convertTofloat64Rate(ch)
	close(ch)
	val, got := <-rch
	if got {
		t.Fatalf("Expected empty channel, got %v", val)
	}
}

func TestSingleRateConversion(t *testing.T) {
	ch := make(chan ptrval, 1)
	rch := convertTofloat64Rate(ch)
	ch <- ptrval{nil, &nextValue, true}
	close(ch)
	val, got := <-rch
	if got {
		t.Fatalf("Expected empty channel, got %v", val)
	}
}

func TestPairRateConversion(t *testing.T) {
	ch := make(chan ptrval, 2)
	rch := convertTofloat64Rate(ch)

	tm := time.Now().UTC()
	val1 := "20"
	ch <- ptrval{couchstore.NewDocInfo(tm.Format(time.RFC3339Nano), 0),
		val1, true}

	tm = tm.Add(5 * time.Second)
	val2 := "25"
	ch <- ptrval{couchstore.NewDocInfo(tm.Format(time.RFC3339Nano), 0),
		val2, false}

	close(ch)
	exp := 1.0
	val, got := <-rch
	if !got {
		t.Fatalf("Expected value, got empty channel")
	}
	if val != exp {
		t.Fatalf("Expected %v, got %v", exp, val)
	}
}

func TestReducers(t *testing.T) {
	tests := []struct {
		reducer string
		exp     interface{}
	}{
		{"any", "31"},
		{"count", 4},
		{"sum", float64(111)},
		{"sumsq", float64(5219)},
		{"max", float64(63)},
		{"min", float64(17)},
		{"avg", float64(37)},
		{"c_min", float64(-23)},
		{"c_avg", float64(7)},
		{"c_max", float64(32)},
		{"identity", testInput},
	}

	for _, test := range tests {
		got := reducers[test.reducer](streamCollection(testInput))
		if !reflect.DeepEqual(got, test.exp) {
			t.Errorf("Expected %v for %v, got %v",
				test.exp, test.reducer, got)
			t.Fail()
		}
	}
}

func TestEmptyReducers(t *testing.T) {
	emptyInput := []interface{}{}
	tests := []struct {
		reducer string
		exp     interface{}
	}{
		{"any", nil},
		{"count", 0},
		{"sum", 0.0},
		{"sumsq", 0.0},
		{"max", math.NaN()},
		{"min", math.NaN()},
		{"avg", math.NaN()},
		{"c_min", math.NaN()},
		{"c_avg", math.NaN()},
		{"c_max", math.NaN()},
		{"identity", emptyInput},
	}

	eq := func(a, b interface{}) bool {
		if !reflect.DeepEqual(a, b) {
			af, aok := a.(float64)
			bf, bok := b.(float64)
			return aok && bok && (math.IsNaN(af) == math.IsNaN(bf))
		}
		return true
	}

	for _, test := range tests {
		got := reducers[test.reducer](streamCollection(emptyInput))
		if !eq(got, test.exp) {
			t.Errorf("Expected %v for %v, got %v",
				test.exp, test.reducer, got)
			t.Fail()
		}
	}
}

func TestNilReducers(t *testing.T) {
	emptyInput := []interface{}{}
	tests := []struct {
		reducer string
		exp     interface{}
	}{
		{"any", nil},
		{"count", 0},
		{"sum", 0.0},
		{"sumsq", 0.0},
		{"max", math.NaN()},
		{"min", math.NaN()},
		{"avg", math.NaN()},
		{"c_min", math.NaN()},
		{"c_avg", math.NaN()},
		{"c_max", math.NaN()},
		{"identity", emptyInput},
	}

	eq := func(a, b interface{}) bool {
		if !reflect.DeepEqual(a, b) {
			af, aok := a.(float64)
			bf, bok := b.(float64)
			return aok && bok && (math.IsNaN(af) == math.IsNaN(bf))
		}
		return true
	}

	for _, test := range tests {
		got := reducers[test.reducer](streamCollection(emptyInput))
		if !eq(got, test.exp) {
			t.Errorf("Expected %v for %v, got %v",
				test.exp, test.reducer, got)
			t.Fail()
		}
	}
}

func TestPointers(t *testing.T) {
	docId := "2013-02-22T16:29:19.750264Z"
	di := couchstore.NewDocInfo(docId, 0)
	tests := []struct {
		pointer string
		exp     interface{}
	}{
		{"/kind", "Listing"},
		{"_id", docId},
	}

	for _, test := range tests {
		chans := make([]chan ptrval, 0, 1)
		chans = append(chans, make(chan ptrval))
		go processDoc(di, chans, bigInput, []string{test.pointer}, []string{}, []string{}, true)
		got := <-chans[0]
		if test.exp != got.val {
			t.Errorf("Expected %v for %v, got %v",
				test.exp, test.pointer, got.val)
		}
	}

}
<|endoftext|>


<commit_before>package auth

import (
	"context"
	"database/sql"
	"strings"
	"time"

	"github.com/rs/xid"

	"github.com/gilcrest/go-API-template/env"
	"github.com/gilcrest/go-API-template/errors"
	"github.com/gilcrest/go-API-template/server/response"
)

type clientID string

// CreateClientRequest is used for the POST /client API request
type CreateClientRequest struct {
	ClientName        string `json:"client_name"`
	ClientHomeURL     string `json:"homepage_url"`
	ClientDescription string `json:"client_description"`
	RedirectURI       string `json:"redirect_uri"`
	Username          string `json:"username"`
}

// ClientResponse is used for the /client API responses
type ClientResponse struct {
	ClientID          string          `json:"client_id"`
	ClientName        string          `json:"client_name"`
	ClientHomeURL     string          `json:"homepage_url"`
	ClientDescription string          `json:"client_description"`
	RedirectURI       string          `json:"redirect_uri"`
	PrimaryUserID     string          `json:"username"`
	ClientSecret      string          `json:"client_secret"`
	ServerToken       string          `json:"server_token"`
	DMLTime           int64           `json:"dml_unix_time"`
	Audit             *response.Audit `json:"audit"`
}

// Client is used for the client service and response
type Client struct {
	id            clientID
	Name          string
	HomeURL       string
	Description   string
	RedirectURI   string
	PrimaryUserID string
	secret        string
	serverToken   string
	DMLTime       time.Time
}

// grant_types     VARCHAR(80),
// scope           VARCHAR(4000),

// ID is a getter for Client.id
func (c *Client) ID() string {
	return string(c.id)
}

// SetID is a setter for Client.id
func (c *Client) SetID() {
	c.id = clientID(xid.New().String())
}

// Secret is a getter for Client.secret
func (c *Client) Secret() string {
	return c.secret
}

// SetSecret is a setter for Client.secret
func (c *Client) SetSecret() {
	c.secret = strings.Join([]string{xid.New().String(), xid.New().String()}, "-")
}

// ServerToken is a getter for Client.serverToken
func (c *Client) ServerToken() string {
	return c.serverToken
}

// SetServerToken is a setter for Client.serverToken
func (c *Client) SetServerToken() error {
	const op errors.Op = "auth.SetServerToken"

	token, err := ServerToken()
	if err != nil {
		return errors.E(op, err)
	}
	c.serverToken = token
	return nil
}

// NewClient is a constructor for the Client struct
func NewClient(ctx context.Context, env *env.Env, c *CreateClientRequest) (*Client, error) {
	const op errors.Op = "auth.CreateClient"

	client := new(Client)

	client.Name = c.ClientName
	client.HomeURL = c.ClientHomeURL
	client.Description = c.ClientDescription
	client.RedirectURI = c.RedirectURI
	client.PrimaryUserID = c.Username

	client.SetID()
	client.SetSecret()
	client.SetServerToken()

	return client, nil
}

// CreateClientDB creates a client/app in the database
func (c *Client) CreateClientDB(ctx context.Context, tx *sql.Tx) (*sql.Tx, error) {
	const op errors.Op = "auth.CreateClientDB"

	var dmlTime time.Time

	// Prepare the sql statement using bind variables
	stmt, err := tx.PrepareContext(ctx, `select demo.create_client (
		p_client_id => $1,
		p_client_name => $2,
		p_homepage_url => $3,
		p_app_description => $4,
		p_redirect_uri => $5,
		p_client_secret => $6,
		p_server_token => $7,
		p_user_id => $8)`)

	if err != nil {
		return nil, errors.E(op, err)
	}
	defer stmt.Close()

	// Execute stored function that returns the create_date timestamp,
	// hence the use of QueryContext instead of Exec
	rows, err := stmt.QueryContext(ctx,
		c.id,            //$1
		c.Name,          //$2
		c.HomeURL,       //$3
		c.Description,   //$4
		c.RedirectURI,   //$5
		c.secret,        //$6
		c.serverToken,   //$7
		c.PrimaryUserID) //$8

	if err != nil {
		return nil, errors.E(op, err)
	}
	defer rows.Close()

	// Iterate through the returned record(s)
	for rows.Next() {
		if err := rows.Scan(&dmlTime); err != nil {
			return nil, errors.E(op, err)
		}
	}

	if err := rows.Err(); err != nil {
		return nil, errors.E(op, err)
	}

	// set the DMLTime field to the create_date set as part of the insert in
	// the stored function call above
	c.DMLTime = dmlTime

	return tx, nil

}
<commit_msg>switch to httplog<commit_after>package auth

import (
	"context"
	"database/sql"
	"strings"
	"time"

	"github.com/rs/xid"

	"github.com/gilcrest/go-API-template/env"
	"github.com/gilcrest/go-API-template/errors"
	"github.com/gilcrest/httplog"
)

type clientID string

// CreateClientRequest is used for the POST /client API request
type CreateClientRequest struct {
	ClientName        string `json:"client_name"`
	ClientHomeURL     string `json:"homepage_url"`
	ClientDescription string `json:"client_description"`
	RedirectURI       string `json:"redirect_uri"`
	Username          string `json:"username"`
}

// ClientResponse is used for the /client API responses
type ClientResponse struct {
	ClientID          string         `json:"client_id"`
	ClientName        string         `json:"client_name"`
	ClientHomeURL     string         `json:"homepage_url"`
	ClientDescription string         `json:"client_description"`
	RedirectURI       string         `json:"redirect_uri"`
	PrimaryUserID     string         `json:"username"`
	ClientSecret      string         `json:"client_secret"`
	ServerToken       string         `json:"server_token"`
	DMLTime           int64          `json:"dml_unix_time"`
	Audit             *httplog.Audit `json:"audit"`
}

// Client is used for the client service and response
type Client struct {
	id            clientID
	Name          string
	HomeURL       string
	Description   string
	RedirectURI   string
	PrimaryUserID string
	secret        string
	serverToken   string
	DMLTime       time.Time
}

// grant_types     VARCHAR(80),
// scope           VARCHAR(4000),

// ID is a getter for Client.id
func (c *Client) ID() string {
	return string(c.id)
}

// SetID is a setter for Client.id
func (c *Client) SetID() {
	c.id = clientID(xid.New().String())
}

// Secret is a getter for Client.secret
func (c *Client) Secret() string {
	return c.secret
}

// SetSecret is a setter for Client.secret
func (c *Client) SetSecret() {
	c.secret = strings.Join([]string{xid.New().String(), xid.New().String()}, "-")
}

// ServerToken is a getter for Client.serverToken
func (c *Client) ServerToken() string {
	return c.serverToken
}

// SetServerToken is a setter for Client.serverToken
func (c *Client) SetServerToken() error {
	const op errors.Op = "auth.SetServerToken"

	token, err := ServerToken()
	if err != nil {
		return errors.E(op, err)
	}
	c.serverToken = token
	return nil
}

// NewClient is a constructor for the Client struct
func NewClient(ctx context.Context, env *env.Env, c *CreateClientRequest) (*Client, error) {
	const op errors.Op = "auth.CreateClient"

	client := new(Client)

	client.Name = c.ClientName
	client.HomeURL = c.ClientHomeURL
	client.Description = c.ClientDescription
	client.RedirectURI = c.RedirectURI
	client.PrimaryUserID = c.Username

	client.SetID()
	client.SetSecret()
	client.SetServerToken()

	return client, nil
}

// CreateClientDB creates a client/app in the database
func (c *Client) CreateClientDB(ctx context.Context, tx *sql.Tx) (*sql.Tx, error) {
	const op errors.Op = "auth.CreateClientDB"

	var dmlTime time.Time

	// Prepare the sql statement using bind variables
	stmt, err := tx.PrepareContext(ctx, `select demo.create_client (
		p_client_id => $1,
		p_client_name => $2,
		p_homepage_url => $3,
		p_app_description => $4,
		p_redirect_uri => $5,
		p_client_secret => $6,
		p_server_token => $7,
		p_user_id => $8)`)

	if err != nil {
		return nil, errors.E(op, err)
	}
	defer stmt.Close()

	// Execute stored function that returns the create_date timestamp,
	// hence the use of QueryContext instead of Exec
	rows, err := stmt.QueryContext(ctx,
		c.id,            //$1
		c.Name,          //$2
		c.HomeURL,       //$3
		c.Description,   //$4
		c.RedirectURI,   //$5
		c.secret,        //$6
		c.serverToken,   //$7
		c.PrimaryUserID) //$8

	if err != nil {
		return nil, errors.E(op, err)
	}
	defer rows.Close()

	// Iterate through the returned record(s)
	for rows.Next() {
		if err := rows.Scan(&dmlTime); err != nil {
			return nil, errors.E(op, err)
		}
	}

	if err := rows.Err(); err != nil {
		return nil, errors.E(op, err)
	}

	// set the DMLTime field to the create_date set as part of the insert in
	// the stored function call above
	c.DMLTime = dmlTime

	return tx, nil

}
<|endoftext|>


<commit_before>// Package aws provides core functionality for making requests to AWS services.
package aws

// SDKName is the name of this AWS SDK
const SDKName = "aws-sdk-go"

// SDKVersion is the version of this SDK
const SDKVersion = "1.0.7"
<commit_msg>Tag release v1.0.8<commit_after>// Package aws provides core functionality for making requests to AWS services.
package aws

// SDKName is the name of this AWS SDK
const SDKName = "aws-sdk-go"

// SDKVersion is the version of this SDK
const SDKVersion = "1.0.8"
<|endoftext|>


<commit_before>package ws

import (
	"github.com/CotaPreco/Horus/command"
	"github.com/CotaPreco/Horus/message"
	"github.com/CotaPreco/Horus/tag"
	tagUtil "github.com/CotaPreco/Horus/tag/util"
	"github.com/CotaPreco/Horus/util"
	wst "github.com/CotaPreco/Horus/ws/tag"
	"github.com/gorilla/websocket"
)

type TaggedConnectionHub struct {
	util.Observer
	command.CommandHandler
	connections map[*websocket.Conn][]tag.Tag
}

func NewTaggedConnectionHub() *TaggedConnectionHub {
	return &TaggedConnectionHub{
		connections: make(map[*websocket.Conn][]tag.Tag),
	}
}

func (h *TaggedConnectionHub) Unsubscribe(connection *websocket.Conn) {
	if h.hasConnection(connection) {
		delete(h.connections, connection)
	}
}

func (h *TaggedConnectionHub) Subscribe(connection *websocket.Conn) {
	h.connections[connection] = make([]tag.Tag, 0)
}

func (h *TaggedConnectionHub) Send(msg message.MessageInterface) {
	if msg == nil {
		return
	}

	switch msg.(type) {
	// ...broadcast
	case *message.Message:
		var m = msg.(*message.Message)

		for connection, _ := range h.connections {
			connection.WriteMessage(websocket.TextMessage, m.Payload)
		}
		break
	// ...contains a particular tag
	case *message.TaggedMessage:
		var m = msg.(*message.TaggedMessage)

		for connection, tags := range h.connections {
			for _, tag := range tags {
				if tag.String() == m.Tag.String() {
					connection.WriteMessage(websocket.TextMessage, m.Payload)
				}
			}
		}
		break

	// ...contains all tags
	case *message.TagSequencedMessage:
		var m = msg.(*message.TagSequencedMessage)

		for connection, tags := range h.connections {
			if tagUtil.ContainsAllTags(m.Tags, tags) {
				connection.WriteMessage(websocket.TextMessage, m.Payload)
			}
		}
		break
	}
}

// `util.Observer`
func (h *TaggedConnectionHub) Update(args ...interface{}) {
	h.Send(args[0].(message.MessageInterface))
}

// `command.CommandHandler`
func (h *TaggedConnectionHub) CanHandle(cmd command.Command) bool {
	switch cmd.(type) {
	case *wst.ATAGCommand:
		return true
	case *wst.RTAGCommand:
		return true
	}

	return false
}

// `command.CommandHandler`
func (h *TaggedConnectionHub) Handle(cmd command.Command) {
	switch cmd.(type) {
	case *wst.ATAGCommand:
		var c = cmd.(*wst.ATAGCommand)

		for _, tag := range h.collectTagsToAdd(c.Connection, c.Tags) {
			h.connections[c.Connection] = append(
				h.connections[c.Connection],
				tag,
			)
		}
		break
	case *wst.RTAGCommand:
		var c = cmd.(*wst.RTAGCommand)

		if !h.hasConnection(c.Connection) {
			return
		}

		// > x y z a b c
		// RTAG x b c
		// > y z a

		var retain []tag.Tag

		for _, remove := range c.Tags {
			for _, tag := range h.connections[c.Connection] {
				if tag.String() != remove.String() {
					retain = append(retain, tag)
				}
			}
		}

		h.connections[c.Connection] = retain
		break
	}
}

func (h *TaggedConnectionHub) collectTagsToAdd(
	connection *websocket.Conn,
	tags []tag.Tag,
) []tag.Tag {
	var add []tag.Tag

	if !h.hasConnection(connection) {
		return add
	}

	for _, candidate := range tags {
		if !tagUtil.ContainsTag(candidate, h.connections[connection]) {
			add = append(add, candidate)
		}
	}

	return add
}

func (h *TaggedConnectionHub) hasConnection(connection *websocket.Conn) bool {
	for conn, _ := range h.connections {
		if conn == connection {
			return true
		}
	}

	return false
}
<commit_msg>Using previously defined utility for delivering `TaggedMessage`<commit_after>package ws

import (
	"github.com/CotaPreco/Horus/command"
	"github.com/CotaPreco/Horus/message"
	"github.com/CotaPreco/Horus/tag"
	tutil "github.com/CotaPreco/Horus/tag/util"
	"github.com/CotaPreco/Horus/util"
	wst "github.com/CotaPreco/Horus/ws/tag"
	"github.com/gorilla/websocket"
)

type TaggedConnectionHub struct {
	util.Observer
	command.CommandHandler
	connections map[*websocket.Conn][]tag.Tag
}

func NewTaggedConnectionHub() *TaggedConnectionHub {
	return &TaggedConnectionHub{
		connections: make(map[*websocket.Conn][]tag.Tag),
	}
}

func (h *TaggedConnectionHub) Unsubscribe(connection *websocket.Conn) {
	if h.hasConnection(connection) {
		delete(h.connections, connection)
	}
}

func (h *TaggedConnectionHub) Subscribe(connection *websocket.Conn) {
	h.connections[connection] = make([]tag.Tag, 0)
}

func (h *TaggedConnectionHub) Send(msg message.MessageInterface) {
	if msg == nil {
		return
	}

	switch msg.(type) {
	// ...broadcast
	case *message.Message:
		var m = msg.(*message.Message)

		for connection, _ := range h.connections {
			connection.WriteMessage(websocket.TextMessage, m.Payload)
		}
		break
	// ...contains a particular tag
	case *message.TaggedMessage:
		var m = msg.(*message.TaggedMessage)

		for connection, tags := range h.connections {
			if tutil.ContainsTag(m.Tag, tags) {
				connection.WriteMessage(websocket.TextMessage, m.Payload)
			}
		}
		break

	// ...contains all tags
	case *message.TagSequencedMessage:
		var m = msg.(*message.TagSequencedMessage)

		for connection, tags := range h.connections {
			if tutil.ContainsAllTags(m.Tags, tags) {
				connection.WriteMessage(websocket.TextMessage, m.Payload)
			}
		}
		break
	}
}

// `util.Observer`
func (h *TaggedConnectionHub) Update(args ...interface{}) {
	h.Send(args[0].(message.MessageInterface))
}

// `command.CommandHandler`
func (h *TaggedConnectionHub) CanHandle(cmd command.Command) bool {
	switch cmd.(type) {
	case *wst.ATAGCommand:
		return true
	case *wst.RTAGCommand:
		return true
	}

	return false
}

// `command.CommandHandler`
func (h *TaggedConnectionHub) Handle(cmd command.Command) {
	switch cmd.(type) {
	case *wst.ATAGCommand:
		var c = cmd.(*wst.ATAGCommand)

		for _, tag := range h.collectTagsToAdd(c.Connection, c.Tags) {
			h.connections[c.Connection] = append(
				h.connections[c.Connection],
				tag,
			)
		}
		break
	case *wst.RTAGCommand:
		var c = cmd.(*wst.RTAGCommand)

		if !h.hasConnection(c.Connection) {
			return
		}

		// > x y z a b c
		// RTAG x b c
		// > y z a

		var retain []tag.Tag

		for _, remove := range c.Tags {
			for _, tag := range h.connections[c.Connection] {
				if tag.String() != remove.String() {
					retain = append(retain, tag)
				}
			}
		}

		h.connections[c.Connection] = retain
		break
	}
}

func (h *TaggedConnectionHub) collectTagsToAdd(
	connection *websocket.Conn,
	tags []tag.Tag,
) []tag.Tag {
	var add []tag.Tag

	if !h.hasConnection(connection) {
		return add
	}

	for _, candidate := range tags {
		if !tutil.ContainsTag(candidate, h.connections[connection]) {
			add = append(add, candidate)
		}
	}

	return add
}

func (h *TaggedConnectionHub) hasConnection(connection *websocket.Conn) bool {
	for conn, _ := range h.connections {
		if conn == connection {
			return true
		}
	}

	return false
}
<|endoftext|>


<commit_before>package rdf

import (
	"github.com/deltamobile/goraptor"
	"github.com/vladvelici/spdx-go/spdx"
	"io"
	"os"
	"strconv"
)

// Writes the input to the specified RDF format
func WriteRdf(input io.Reader, output io.Writer, format string) error {
	parser := goraptor.NewParser("guess")
	defer parser.Free()

	serializer := goraptor.NewSerializer(format)
	defer serializer.Free()

	parser.SetNamespaceHandler(func(pfx, uri string) { serializer.SetNamespace(pfx, uri) })

	statements := parser.Parse(input, "")

	str, err := serializer.Serialize(statements, "")
	if err != nil {
		return err
	}

	_, err = io.WriteString(output, str)
	return err
}

// Used to write documents in RDF format
type Formatter struct {
	serializer *goraptor.Serializer
	nodeIds    map[string]int
}

// Create a new Formatter that writes to output
func NewFormatter(output *os.File, format string) *Formatter {
	s := goraptor.NewSerializer(format)
	s.StartStream(output, baseUri)
	return &Formatter{
		serializer: s,
		nodeIds:    make(map[string]int),
	}
}

// Create a new node id for the given prefix
func (f *Formatter) newId(prefix string) *goraptor.Blank {
	f.nodeIds[prefix]++
	id := goraptor.Blank(prefix + strconv.Itoa(f.nodeIds[prefix]))
	return &id
}

// Sets the type t to node
func (f *Formatter) setType(node goraptor.Term, t string) error {
	return f.add(node, prefix("ns:type"), prefix(t))
}

// Add `key`=`value` at object `to`.
func (f *Formatter) add(to, key, value goraptor.Term) error {
	return f.serializer.Add(&goraptor.Statement{
		Subject:   to,
		Predicate: key,
		Object:    value,
	})
}

// Using the SPDX baseUri, add a goraptor.Term.
func (f *Formatter) addTerm(to goraptor.Term, key string, value goraptor.Term) error {
	return f.add(to, prefix(key), value)
}

// Using the SPDX baseUri, add pairs of literals.
func (f *Formatter) addPairs(to goraptor.Term, pairs ...pair) error {
	for _, p := range pairs {
		if err := f.addLiteral(to, p.key, p.val); err != nil {
			return err
		}
	}
	return nil
}

// Using the SPDX baseUri, add one literal. Does not write anything and returns nil if the value is empty.
func (f *Formatter) addLiteral(to goraptor.Term, key, value string) error {
	if value == "" {
		return nil
	}
	return f.add(to, prefix(key), &goraptor.Literal{Value: value})
}

// Write a document.
func (f *Formatter) Document(doc *spdx.Document) (docId goraptor.Term, err error) {
	_docId := goraptor.Blank("doc")
	docId = &_docId

	if err = f.setType(docId, "SpdxDocument"); err != nil {
		return
	}

	if err = f.addLiteral(docId, "specVersion", doc.SpecVersion.Val); err != nil {
		return
	}

	if err = f.addTerm(docId, "dataLicense", uri(licenceUri+doc.DataLicence.Val)); err != nil {
		return
	}

	if id, err := f.CreationInfo(doc.CreationInfo); err == nil {
		if err = f.addTerm(docId, "creationInfo", id); err != nil {
			return docId, err
		}
	} else {
		return docId, err
	}

	if err = f.addLiteral(docId, "rdfs:comment", doc.Comment.Val); err != nil {
		return
	}

	/*
		if err = f.Reviews(docId, "reviewed", doc.Reviews); err != nil {
			return
		}
	*/
	if err = f.Packages(docId, "describesPackage", doc.Packages); err != nil {
		return
	}

	/*
	   if err = f.Files(docId, "referencesFile", doc.Files); err != nil {
	       return
	   }

	   if err = f.ExtrLicInfos(docId, "hasExtractedLicensingInfo", doc.ExtractedLicenceInfo); err != nil {
	       return
	   }
	*/

	return docId, nil
}

// Write creation info.
func (f *Formatter) CreationInfo(cr *spdx.CreationInfo) (id goraptor.Term, err error) {
	id = f.newId("cri")

	if err = f.setType(id, "CreationInfo"); err != nil {
		return
	}

	err = f.addPairs(id,
		pair{"created", cr.Created.V()},
		pair{"rdfs:comment", cr.Comment.V()},
		pair{"licenseListVersion", cr.LicenceListVersion.V()},
	)

	if err != nil {
		return
	}

	for _, creator := range cr.Creator {
		if err = f.addLiteral(id, "creator", creator.V()); err != nil {
			return
		}
	}

	return id, nil
}

// Write a slice of reviews.
func (f *Formatter) Reviews(rs []*spdx.Review) error {
	for _, r := range rs {
		if err := f.Review(r); err != nil {
			return err
		}
	}
	return nil
}

// Write a review.
func (f *Formatter) Review(r *spdx.Review) error {
	return nil
}

// Write a slice of packages.
func (f *Formatter) Packages(parent goraptor.Term, element string, pkgs []*spdx.Package) error {
	if len(pkgs) == 0 {
		return nil
	}
	for _, pkg := range pkgs {
		pkgid, err := f.Package(pkg)
		if err != nil {
			return err
		}
		if err = f.addTerm(parent, element, pkgid); err != nil {
			return err
		}
	}
	return nil
}

// Write a package.
func (f *Formatter) Package(pkg *spdx.Package) (id goraptor.Term, err error) {
	id = f.newId("pkg")

	if err = f.setType(id, "Package"); err != nil {
		return
	}

	err = f.addPairs(id,
		pair{"name", pkg.Name.Val},
		pair{"versionInfo", pkg.Version.Val},
		pair{"packageFileName", pkg.FileName.Val},
		pair{"supplier", pkg.Supplier.V()},
		pair{"originator", pkg.Originator.V()},
		pair{"downloadLocation", pkg.DownloadLocation.Val},
		pair{"doap:homepage", pkg.HomePage.Val},
		pair{"sourceInfo", pkg.SourceInfo.Val},
		pair{"licenseComments", pkg.LicenceComments.Val},
		pair{"copyrightText", pkg.CopyrightText.Val},
		pair{"summary", pkg.Summary.Val},
		pair{"description", pkg.Description.Val},
	)
	if err != nil {
		return
	}

	if pkg.VerificationCode != nil {
		pkgid, err := f.VerificationCode(pkg.VerificationCode)
		if err != nil {
			return id, err
		}
		if err = f.addTerm(id, "packageVerificationCode", pkgid); err != nil {
			return id, err
		}
	}
	return id, nil
}

func (f *Formatter) VerificationCode(vc *spdx.VerificationCode) (id goraptor.Term, err error) {
	id = f.newId("vc")

	if err = f.setType(id, "PackageVerificationCode"); err != nil {
		return
	}

	err = f.addLiteral(id, "packageVerificationCodeValue", vc.Value.Val)
	if err != nil {
		return
	}

	for _, excl := range vc.ExcludedFiles {
		err = f.addLiteral(id, "packageVerificationCodeExcludedFile", excl.Val)
		if err != nil {
			return
		}
	}

	return id, nil
}

// Write a slice of ExtractedLicensingInfo
func (f *Formatter) ExtrLicInfos(lics []*spdx.ExtractedLicensingInfo) error {
	for _, lic := range lics {
		if err := f.ExtrLicInfo(lic); err != nil {
			return err
		}
	}
	return nil
}

// Write an ExtractedLicensingInfo
func (f *Formatter) ExtrLicInfo(lic *spdx.ExtractedLicensingInfo) error {
	return nil
}

// Write a slice of files.
func (f *Formatter) Files(files []*spdx.File) error {
	for _, file := range files {
		if err := f.File(file); err != nil {
			return err
		}
	}
	return nil
}

// Write a file.
func (f *Formatter) File(file *spdx.File) error {
	return nil
}

// Closes the stream and frees the serializer. Always call after writing using the Formatter.
func (f *Formatter) Close() {
	f.serializer.EndStream()
	f.serializer.Free()
}
<commit_msg>Update rdf writer skeleton<commit_after>package rdf

import (
	"github.com/deltamobile/goraptor"
	"github.com/vladvelici/spdx-go/spdx"
	"io"
	"os"
	"strconv"
	"strings"
)

// Writes the input to the specified RDF format
func WriteRdf(input io.Reader, output io.Writer, format string) error {
	parser := goraptor.NewParser("guess")
	defer parser.Free()

	serializer := goraptor.NewSerializer(format)
	defer serializer.Free()

	parser.SetNamespaceHandler(func(pfx, uri string) { serializer.SetNamespace(pfx, uri) })

	statements := parser.Parse(input, "")

	str, err := serializer.Serialize(statements, "")
	if err != nil {
		return err
	}

	_, err = io.WriteString(output, str)
	return err
}

// Used to write documents in RDF format
type Formatter struct {
	serializer *goraptor.Serializer
	nodeIds    map[string]int
}

// Create a new Formatter that writes to output
func NewFormatter(output *os.File, format string) *Formatter {
	s := goraptor.NewSerializer(format)
	s.StartStream(output, baseUri)
	return &Formatter{
		serializer: s,
		nodeIds:    make(map[string]int),
	}
}

// Create a new node id for the given prefix
func (f *Formatter) newId(prefix string) *goraptor.Blank {
	f.nodeIds[prefix]++
	id := goraptor.Blank(prefix + strconv.Itoa(f.nodeIds[prefix]))
	return &id
}

// Sets the type t to node
func (f *Formatter) setType(node goraptor.Term, t string) error {
	return f.add(node, prefix("ns:type"), prefix(t))
}

// Add `key`=`value` at object `to`.
func (f *Formatter) add(to, key, value goraptor.Term) error {
	return f.serializer.Add(&goraptor.Statement{
		Subject:   to,
		Predicate: key,
		Object:    value,
	})
}

// Using the SPDX baseUri, add a goraptor.Term.
func (f *Formatter) addTerm(to goraptor.Term, key string, value goraptor.Term) error {
	return f.add(to, prefix(key), value)
}

// Using the SPDX baseUri, add pairs of literals.
func (f *Formatter) addPairs(to goraptor.Term, pairs ...pair) error {
	for _, p := range pairs {
		if err := f.addLiteral(to, p.key, p.val); err != nil {
			return err
		}
	}
	return nil
}

// Using the SPDX baseUri, add one literal. Does not write anything and returns nil if the value is empty.
func (f *Formatter) addLiteral(to goraptor.Term, key, value string) error {
	if value == "" {
		return nil
	}
	return f.add(to, prefix(key), &goraptor.Literal{Value: value})
}

// Write a document.
func (f *Formatter) Document(doc *spdx.Document) (docId goraptor.Term, err error) {
	_docId := goraptor.Blank("doc")
	docId = &_docId

	if err = f.setType(docId, "SpdxDocument"); err != nil {
		return
	}

	if err = f.addLiteral(docId, "specVersion", doc.SpecVersion.Val); err != nil {
		return
	}

	if err = f.addTerm(docId, "dataLicense", uri(licenceUri+doc.DataLicence.Val)); err != nil {
		return
	}

	if id, err := f.CreationInfo(doc.CreationInfo); err == nil {
		if err = f.addTerm(docId, "creationInfo", id); err != nil {
			return docId, err
		}
	} else {
		return docId, err
	}

	if err = f.addLiteral(docId, "rdfs:comment", doc.Comment.Val); err != nil {
		return
	}

	if err = f.Reviews(docId, "reviewed", doc.Reviews); err != nil {
		return
	}

	if err = f.Packages(docId, "describesPackage", doc.Packages); err != nil {
		return
	}

	if err = f.Files(docId, "referencesFile", doc.Files); err != nil {
		return
	}

	if err = f.ExtrLicInfos(docId, "hasExtractedLicensingInfo", doc.ExtractedLicenceInfo); err != nil {
		return
	}

	return docId, nil
}

// Write creation info.
func (f *Formatter) CreationInfo(cr *spdx.CreationInfo) (id goraptor.Term, err error) {
	id = f.newId("cri")

	if err = f.setType(id, "CreationInfo"); err != nil {
		return
	}

	err = f.addPairs(id,
		pair{"created", cr.Created.V()},
		pair{"rdfs:comment", cr.Comment.V()},
		pair{"licenseListVersion", cr.LicenceListVersion.V()},
	)

	if err != nil {
		return
	}

	for _, creator := range cr.Creator {
		if err = f.addLiteral(id, "creator", creator.V()); err != nil {
			return
		}
	}

	return id, nil
}

// Write a slice of reviews.
func (f *Formatter) Reviews(parent goraptor.Term, element string, rs []*spdx.Review) error {
	if len(rs) == 0 {
		return nil
	}
	for _, r := range rs {
		revId, err := f.Review(r)
		if err != nil {
			return err
		}
		if err = f.addTerm(parent, element, revId); err != nil {
			return err
		}
	}
	return nil
}

// Write a review.
func (f *Formatter) Review(r *spdx.Review) (id goraptor.Term, err error) {
	return
}

// Write a slice of packages.
func (f *Formatter) Packages(parent goraptor.Term, element string, pkgs []*spdx.Package) error {
	if len(pkgs) == 0 {
		return nil
	}
	for _, pkg := range pkgs {
		pkgid, err := f.Package(pkg)
		if err != nil {
			return err
		}
		if err = f.addTerm(parent, element, pkgid); err != nil {
			return err
		}
	}
	return nil
}

// Write a package.
func (f *Formatter) Package(pkg *spdx.Package) (id goraptor.Term, err error) {
	id = f.newId("pkg")

	if err = f.setType(id, "Package"); err != nil {
		return
	}

	err = f.addPairs(id,
		pair{"name", pkg.Name.Val},
		pair{"versionInfo", pkg.Version.Val},
		pair{"packageFileName", pkg.FileName.Val},
		pair{"supplier", pkg.Supplier.V()},
		pair{"originator", pkg.Originator.V()},
		pair{"downloadLocation", pkg.DownloadLocation.Val},
		pair{"doap:homepage", pkg.HomePage.Val},
		pair{"sourceInfo", pkg.SourceInfo.Val},
		pair{"licenseComments", pkg.LicenceComments.Val},
		pair{"copyrightText", pkg.CopyrightText.Val},
		pair{"summary", pkg.Summary.Val},
		pair{"description", pkg.Description.Val},
	)
	if err != nil {
		return
	}

	if pkg.VerificationCode != nil {
		pkgid, err := f.VerificationCode(pkg.VerificationCode)
		if err != nil {
			return id, err
		}
		if err = f.addTerm(id, "packageVerificationCode", pkgid); err != nil {
			return id, err
		}
	}

	if pkg.Checksum != nil {
		cksumId, err := f.Checksum(pkg.Checksum)
		if err != nil {
			return id, err
		}
		if err = f.addTerm(id, "checksum", cksumId); err != nil {
			return id, err
		}
	}

	return id, nil
}

// Write a VerificationCode
func (f *Formatter) VerificationCode(vc *spdx.VerificationCode) (id goraptor.Term, err error) {
	id = f.newId("vc")

	if err = f.setType(id, "PackageVerificationCode"); err != nil {
		return
	}

	err = f.addLiteral(id, "packageVerificationCodeValue", vc.Value.Val)
	if err != nil {
		return
	}

	for _, excl := range vc.ExcludedFiles {
		err = f.addLiteral(id, "packageVerificationCodeExcludedFile", excl.Val)
		if err != nil {
			return
		}
	}

	return id, nil
}

// Write a Checksum
func (f *Formatter) Checksum(cksum *spdx.Checksum) (id goraptor.Term, err error) {
	id = f.newId("cksum")

	if err = f.setType(id, "Checksum"); err != nil {
		return
	}

	err = f.addLiteral(id, "checksumValue", cksum.Value.Val)
	if err != nil {
		return
	}

	algo := strings.ToLower(cksum.Algo.Val)
	if algo == "sha1" {
		err = f.addTerm(id, "algorithm", prefix("checksumAlgorithm_sha1"))
	} else {
		err = f.addLiteral(id, "algorithm", algo)
	}

	return id, err
}

// Write a slice of ExtractedLicensingInfo
func (f *Formatter) ExtrLicInfos(parent goraptor.Term, element string, lics []*spdx.ExtractedLicensingInfo) error {
	if len(lics) == 0 {
		return nil
	}
	for _, lic := range lics {
		licId, err := f.ExtrLicInfo(lic)
		if err != nil {
			return err
		}
		if err = f.addTerm(parent, element, licId); err != nil {
			return err
		}
	}
	return nil
}

// Write an ExtractedLicensingInfo
func (f *Formatter) ExtrLicInfo(lic *spdx.ExtractedLicensingInfo) (id goraptor.Term, err error) {
	return
}

// Write a slice of files.
func (f *Formatter) Files(parent goraptor.Term, element string, files []*spdx.File) error {
	if len(files) == 0 {
		return nil
	}
	for _, file := range files {
		fId, err := f.File(file)
		if err != nil {
			return err
		}
		if err = f.addTerm(parent, element, fId); err != nil {
			return err
		}
	}
	return nil
}

// Write a file.
func (f *Formatter) File(file *spdx.File) (id goraptor.Term, err error) {
	return
}

// Closes the stream and frees the serializer. Always call after writing using the Formatter.
func (f *Formatter) Close() {
	f.serializer.EndStream()
	f.serializer.Free()
}
<|endoftext|>


<commit_before>/*
 * The MIT License (MIT)
 * Copyright (c) 2016 Maksym Borodin <borodin.maksym@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions
 * of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
 * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */
package xenserver

import (
	"bytes"
	"fmt"
	"log"
	"strings"

	"github.com/hashicorp/terraform/helper/hashcode"
	"github.com/hashicorp/terraform/helper/schema"
	"github.com/ringods/go-xen-api-client"
)

const (
	vbdSchemaVdiUUID        = "vdi_uuid"
	vbdSchemaBootable       = "bootable"
	vbdSchemaMode           = "mode"
	vbdSchemaUserDevice     = "user_device"
	vbdSchemaTemplateDevice = "is_from_template"
)

func queryTemplateVBDs(c *Connection, vm *VMDescriptor) (vbds []*VBDDescriptor, err error) {
	vbds = make([]*VBDDescriptor, 0)
	var vmVBDRefs []xenAPI.VBDRef
	if vmVBDRefs, err = c.client.VM.GetVBDs(c.session, vm.VMRef); err != nil {
		return
	}

	for _, vmVBDRef := range vmVBDRefs {
		vbd := &VBDDescriptor{
			VBDRef: vmVBDRef,
		}

		if err = vbd.Query(c); err != nil {
			return nil, err
		}

		if vbd.IsTemplateDevice {
			log.Printf("[DEBUG] VBD %s (type = %s) comes from template", vbd.UUID, vbd.Type)
			vbds = append(vbds, vbd)
		}
	}

	log.Printf("[DEBUG] Got %d template vdbs", len(vbds))

	return vbds, nil
}

func readTemplateVBDsToSchema(c *Connection, vm *VMDescriptor, s []interface{}, vbdType xenAPI.VbdType) error {
	var vmVBDRefs []xenAPI.VBDRef
	var err error
	if vmVBDRefs, err = c.client.VM.GetVBDs(c.session, vm.VMRef); err != nil {
		return err
	}

	for _, vmVBDRef := range vmVBDRefs {
		vbd := &VBDDescriptor{
			VBDRef: vmVBDRef,
		}

		if vbd.Query(c) != nil {
			return err
		}

		// Skip unrelevant VBDs
		if vbdType != vbd.Type {
			continue
		}

		found := false
		for _, schm := range s {
			data := schm.(map[string]interface{})
			userDevice := data[vbdSchemaUserDevice].(string)
			isTemplateDevice := data[vbdSchemaTemplateDevice].(bool)

			if isTemplateDevice && userDevice == vbd.UserDevice {
				found = true

				vbd.IsTemplateDevice = isTemplateDevice

				if err = vbd.Commit(c); err != nil {
					return err
				}

				data[vbdSchemaUserDevice] = vbd.UserDevice
				data[vbdSchemaVdiUUID] = vbd.VDI.UUID
				data[vbdSchemaBootable] = vbd.Bootable
				data[vbdSchemaMode] = vbd.Mode
				data[vbdSchemaTemplateDevice] = isTemplateDevice

				break
			}

		}

		if !found {
			return fmt.Errorf("template VBD %s is not referenced", vbd.UUID)
		}
	}

	return nil
}

func destroyTemplateVDIs(c *Connection, vbds []*VBDDescriptor) (err error) {
	log.Println("[DEBUG] Destroying vbds")
	for _, vbd := range vbds {

		// Only relevant to HDDs
		if vbd.Type != xenAPI.VbdTypeDisk {
			continue
		}

		log.Println("[DEBUG] Destroy vbd ", vbd.UUID)
		if err = c.client.VDI.Destroy(c.session, vbd.VDI.VDIRef); err != nil {
			return err
		}
	}
	return nil
}

func readVBDFromSchema(c *Connection, s map[string]interface{}) (*VBDDescriptor, error) {
	// In API it is called user_device, but in terraform provider it is called template device
	// to emphasise that it is used to map VBD from template
	userDevice := s[vbdSchemaUserDevice].(string)

	var vdi *VDIDescriptor = nil

	if id, ok := s[vbdSchemaVdiUUID]; ok {
		log.Println("[DEBUG] Try load VDI ", id)
		vdi = &VDIDescriptor{}
		vdi.UUID = id.(string)
		if err := vdi.Load(c); err != nil {
			return nil, err
		}
	}
	bootable := s[vbdSchemaBootable].(bool)

	var mode xenAPI.VbdMode
	_mode := strings.ToLower(s[vbdSchemaMode].(string))

	if _mode == strings.ToLower(string(xenAPI.VbdModeRO)) {
		mode = xenAPI.VbdModeRO
	} else if _mode == strings.ToLower(string(xenAPI.VbdModeRW)) {
		mode = xenAPI.VbdModeRW
	} else {
		return nil, fmt.Errorf("%q is not valid mode (either RO or RW)", s[vbdSchemaMode].(string))
	}

	vbd := &VBDDescriptor{
		VDI:        vdi,
		Bootable:   bootable,
		Mode:       mode,
		UserDevice: userDevice,
	}

	return vbd, nil
}

func readVBDsFromSchema(c *Connection, s []interface{}) ([]*VBDDescriptor, error) {
	vbds := make([]*VBDDescriptor, 0, len(s))

	for _, schm := range s {
		data := schm.(map[string]interface{})

		var vbd *VBDDescriptor
		var err error
		if vbd, err = readVBDFromSchema(c, data); err != nil {
			return nil, err
		}
		vbds = append(vbds, vbd)
	}

	return vbds, nil
}

func fillVBDSchema(vbd VBDDescriptor) map[string]interface{} {
	uuid := ""
	if vbd.VDI != nil {
		uuid = vbd.VDI.UUID
	}
	return map[string]interface{}{
		vbdSchemaVdiUUID:        uuid,
		vbdSchemaBootable:       vbd.Bootable,
		vbdSchemaMode:           vbd.Mode,
		vbdSchemaUserDevice:     vbd.UserDevice,
		vbdSchemaTemplateDevice: vbd.IsTemplateDevice,
	}
}

func readVBDs(c *Connection, vm *VMDescriptor) ([]map[string]interface{}, []map[string]interface{}, error) {
	vmVBDs, err := c.client.VM.GetVBDs(c.session, vm.VMRef)
	if err != nil {
		return nil, nil, err
	}

	hdd := make([]map[string]interface{}, 0, len(vmVBDs))
	cdrom := make([]map[string]interface{}, 0, len(vmVBDs))
	log.Println(fmt.Sprintf("[DEBUG] Got %d VDIs", len(vmVBDs)))

	for _, _vbd := range vmVBDs {
		vbd := VBDDescriptor{
			VBDRef: _vbd,
		}

		if err := vbd.Query(c); err != nil {
			return nil, nil, err
		}

		log.Println("[DEBUG] Found VBD", vbd.UUID)
		vbdData := fillVBDSchema(vbd)
		log.Println("[DEBUG] VBD: ", vbdData)
		log.Println("[DEBUG] VBD Type: ", vbd.Type)

		switch vbd.Type {
		case xenAPI.VbdTypeCD:
			cdrom = append(cdrom, vbdData)
			break
		case xenAPI.VbdTypeDisk:
			hdd = append(hdd, vbdData)
		default:
			return nil, nil, fmt.Errorf("Unsupported VBD type %q", string(vbd.Type))
		}
	}

	return hdd, cdrom, nil
}

func setSchemaVBDs(c *Connection, vm *VMDescriptor, d *schema.ResourceData) error {
	var err error
	var hdd []map[string]interface{}
	var cdrom []map[string]interface{}
	if hdd, cdrom, err = readVBDs(c, vm); err != nil {
		log.Println("[ERROR] ", err)
		return err
	}

	log.Println("[DEBUG] Found ", len(cdrom), " CDs and ", len(hdd), " HDDs")
	err = d.Set(vmSchemaHardDrive, hdd)
	if err != nil {
		log.Println("[ERROR] ", err)
		return err
	}
	err = d.Set(vmSchemaCdRom, cdrom)
	if err != nil {
		log.Println("[ERROR] ", err)
		return err
	}

	return nil
}

func createVBD(c *Connection, vbd *VBDDescriptor) (*VBDDescriptor, error) {
	log.Println(fmt.Sprintf("[DEBUG] Creating VBD for VM %q", vbd.VM.Name))

	vbdObject := xenAPI.VBDRecord{
		Type:       vbd.Type,
		Mode:       vbd.Mode,
		Bootable:   vbd.Bootable,
		VM:         vbd.VM.VMRef,
		Empty:      vbd.VDI == nil,
		Userdevice: vbd.UserDevice,
	}

	if devices, err := c.client.VM.GetAllowedVBDDevices(c.session, vbd.VM.VMRef); err == nil {
		if len(devices) == 0 {
			return nil, fmt.Errorf("No available devices to attach to")
		}
		vbdObject.Userdevice = devices[0]
		log.Println("[DEBUG] Selected device for VBD: ", vbdObject.Userdevice)
	} else {
		return nil, err
	}

	if vbd.VDI != nil {
		vbdObject.VDI = vbd.VDI.VDIRef
	}

	vbdRef, err := c.client.VBD.Create(c.session, vbdObject)
	if err != nil {
		return nil, err
	}

	log.Println(fmt.Sprintf("[DEBUG] Created VBD"))

	vbd.VBDRef = vbdRef
	err = vbd.Query(c)
	if err != nil {
		return nil, err
	}

	log.Println(fmt.Sprintf("[DEBUG] VBD  UUID %q", vbd.UUID))

	if vbd.VM.PowerState == xenAPI.VMPowerStateRunning {
		err = c.client.VBD.Plug(c.session, vbdRef)
		if err != nil {
			return nil, err
		}

		log.Println(fmt.Sprintf("[DEBUG] Plugged VBD %q to VM %q", vbd.UUID, vbd.VM.Name))
	}

	return vbd, nil
}

func vbdHash(v interface{}) int {
	m := v.(map[string]interface{})
	var buf bytes.Buffer
	var count int = 0
	var b int

	userDevice := m[vbdSchemaUserDevice].(string)
	isTemplateDevice := m[vbdSchemaTemplateDevice].(bool)
	mode := m[vbdSchemaMode].(string)
	bootable := m[vbdSchemaBootable].(bool)
	vdiUUID := m[vbdSchemaVdiUUID].(string)

	log.Println("[DEBUG] Calculating hash for ", v)

	if isTemplateDevice || userDevice != "" {
		b, _ = buf.WriteString(fmt.Sprintf("%s", userDevice))
		count += b
	}

	if !isTemplateDevice {
		b, _ = buf.WriteString(fmt.Sprintf("-%s", vdiUUID))
		count += b

		if mode != "" {
			b, _ = buf.WriteString(fmt.Sprintf("%-s", mode))
			count += b
		}

		b, _ = buf.WriteString(fmt.Sprintf("-%t", bootable))
		count += b
	}
	log.Println("Consumed total ", count, " bytes to generate hash")
	log.Println("String for hash: ", buf.String())

	return hashcode.String(buf.String())
}

func createVBDs(c *Connection, s []interface{}, vbdType xenAPI.VbdType, vm *VMDescriptor) (err error) {

	if err := readTemplateVBDsToSchema(c, vm, s, vbdType); err != nil {
		return err
	}

	log.Println("[DEBUG] Creating ", len(s), " VBDS of type ", vbdType)

	for _, schm := range s {
		data := schm.(map[string]interface{})

		if data[vbdSchemaUserDevice] != "" {
			continue
		}

		var vbd *VBDDescriptor
		var err error
		if vbd, err = readVBDFromSchema(c, data); err != nil {
			return err
		}

		// User Device is used to specify existing VBDs
		if vbd.UserDevice != "" {
			continue
		}

		vbd.Type = vbdType
		vbd.VM = vm

		if vbdType == xenAPI.VbdTypeCD {
			vbd.Mode = xenAPI.VbdModeRO
		}

		if vbd, err = createVBD(c, vbd); err != nil {
			return err
		}

		data[vbdSchemaUserDevice] = vbd.UserDevice
		data[vbdSchemaVdiUUID] = vbd.VDI.UUID
		data[vbdSchemaBootable] = vbd.Bootable
		data[vbdSchemaMode] = vbd.Mode
	}

	return nil
}

func resourceVBD() *schema.Resource {
	return &schema.Resource{

		Schema: map[string]*schema.Schema{
			vbdSchemaTemplateDevice: &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Default:  false,
			},
			vbdSchemaVdiUUID: &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
			vbdSchemaUserDevice: &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
			vbdSchemaBootable: &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Computed: true,
			},
			vbdSchemaMode: &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
		},
	}
}
<commit_msg>Correct debug string<commit_after>/*
 * The MIT License (MIT)
 * Copyright (c) 2016 Maksym Borodin <borodin.maksym@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions
 * of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
 * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */
package xenserver

import (
	"bytes"
	"fmt"
	"log"
	"strings"

	"github.com/hashicorp/terraform/helper/hashcode"
	"github.com/hashicorp/terraform/helper/schema"
	"github.com/ringods/go-xen-api-client"
)

const (
	vbdSchemaVdiUUID        = "vdi_uuid"
	vbdSchemaBootable       = "bootable"
	vbdSchemaMode           = "mode"
	vbdSchemaUserDevice     = "user_device"
	vbdSchemaTemplateDevice = "is_from_template"
)

func queryTemplateVBDs(c *Connection, vm *VMDescriptor) (vbds []*VBDDescriptor, err error) {
	vbds = make([]*VBDDescriptor, 0)
	var vmVBDRefs []xenAPI.VBDRef
	if vmVBDRefs, err = c.client.VM.GetVBDs(c.session, vm.VMRef); err != nil {
		return
	}

	for _, vmVBDRef := range vmVBDRefs {
		vbd := &VBDDescriptor{
			VBDRef: vmVBDRef,
		}

		if err = vbd.Query(c); err != nil {
			return nil, err
		}

		if vbd.IsTemplateDevice {
			log.Printf("[DEBUG] VBD %s (type = %s) comes from template", vbd.UUID, vbd.Type)
			vbds = append(vbds, vbd)
		}
	}

	log.Printf("[DEBUG] Got %d template vdbs", len(vbds))

	return vbds, nil
}

func readTemplateVBDsToSchema(c *Connection, vm *VMDescriptor, s []interface{}, vbdType xenAPI.VbdType) error {
	var vmVBDRefs []xenAPI.VBDRef
	var err error
	if vmVBDRefs, err = c.client.VM.GetVBDs(c.session, vm.VMRef); err != nil {
		return err
	}

	for _, vmVBDRef := range vmVBDRefs {
		vbd := &VBDDescriptor{
			VBDRef: vmVBDRef,
		}

		if vbd.Query(c) != nil {
			return err
		}

		// Skip unrelevant VBDs
		if vbdType != vbd.Type {
			continue
		}

		found := false
		for _, schm := range s {
			data := schm.(map[string]interface{})
			userDevice := data[vbdSchemaUserDevice].(string)
			isTemplateDevice := data[vbdSchemaTemplateDevice].(bool)

			if isTemplateDevice && userDevice == vbd.UserDevice {
				found = true

				vbd.IsTemplateDevice = isTemplateDevice

				if err = vbd.Commit(c); err != nil {
					return err
				}

				data[vbdSchemaUserDevice] = vbd.UserDevice
				data[vbdSchemaVdiUUID] = vbd.VDI.UUID
				data[vbdSchemaBootable] = vbd.Bootable
				data[vbdSchemaMode] = vbd.Mode
				data[vbdSchemaTemplateDevice] = isTemplateDevice

				break
			}

		}

		if !found {
			return fmt.Errorf("template VBD %s is not referenced", vbd.UUID)
		}
	}

	return nil
}

func destroyTemplateVDIs(c *Connection, vbds []*VBDDescriptor) (err error) {
	log.Println("[DEBUG] Destroying vbds")
	for _, vbd := range vbds {

		// Only relevant to HDDs
		if vbd.Type != xenAPI.VbdTypeDisk {
			continue
		}

		log.Println("[DEBUG] Destroy vbd ", vbd.UUID)
		if err = c.client.VDI.Destroy(c.session, vbd.VDI.VDIRef); err != nil {
			return err
		}
	}
	return nil
}

func readVBDFromSchema(c *Connection, s map[string]interface{}) (*VBDDescriptor, error) {
	// In API it is called user_device, but in terraform provider it is called template device
	// to emphasise that it is used to map VBD from template
	userDevice := s[vbdSchemaUserDevice].(string)

	var vdi *VDIDescriptor = nil

	if id, ok := s[vbdSchemaVdiUUID]; ok {
		log.Println("[DEBUG] Try load VDI ", id)
		vdi = &VDIDescriptor{}
		vdi.UUID = id.(string)
		if err := vdi.Load(c); err != nil {
			return nil, err
		}
	}
	bootable := s[vbdSchemaBootable].(bool)

	var mode xenAPI.VbdMode
	_mode := strings.ToLower(s[vbdSchemaMode].(string))

	if _mode == strings.ToLower(string(xenAPI.VbdModeRO)) {
		mode = xenAPI.VbdModeRO
	} else if _mode == strings.ToLower(string(xenAPI.VbdModeRW)) {
		mode = xenAPI.VbdModeRW
	} else {
		return nil, fmt.Errorf("%q is not valid mode (either RO or RW)", s[vbdSchemaMode].(string))
	}

	vbd := &VBDDescriptor{
		VDI:        vdi,
		Bootable:   bootable,
		Mode:       mode,
		UserDevice: userDevice,
	}

	return vbd, nil
}

func readVBDsFromSchema(c *Connection, s []interface{}) ([]*VBDDescriptor, error) {
	vbds := make([]*VBDDescriptor, 0, len(s))

	for _, schm := range s {
		data := schm.(map[string]interface{})

		var vbd *VBDDescriptor
		var err error
		if vbd, err = readVBDFromSchema(c, data); err != nil {
			return nil, err
		}
		vbds = append(vbds, vbd)
	}

	return vbds, nil
}

func fillVBDSchema(vbd VBDDescriptor) map[string]interface{} {
	uuid := ""
	if vbd.VDI != nil {
		uuid = vbd.VDI.UUID
	}
	return map[string]interface{}{
		vbdSchemaVdiUUID:        uuid,
		vbdSchemaBootable:       vbd.Bootable,
		vbdSchemaMode:           vbd.Mode,
		vbdSchemaUserDevice:     vbd.UserDevice,
		vbdSchemaTemplateDevice: vbd.IsTemplateDevice,
	}
}

func readVBDs(c *Connection, vm *VMDescriptor) ([]map[string]interface{}, []map[string]interface{}, error) {
	vmVBDs, err := c.client.VM.GetVBDs(c.session, vm.VMRef)
	if err != nil {
		return nil, nil, err
	}

	hdd := make([]map[string]interface{}, 0, len(vmVBDs))
	cdrom := make([]map[string]interface{}, 0, len(vmVBDs))
	log.Println(fmt.Sprintf("[DEBUG] Got %d VBDs", len(vmVBDs)))

	for _, _vbd := range vmVBDs {
		vbd := VBDDescriptor{
			VBDRef: _vbd,
		}

		if err := vbd.Query(c); err != nil {
			return nil, nil, err
		}

		log.Println("[DEBUG] Found VBD", vbd.UUID)
		vbdData := fillVBDSchema(vbd)
		log.Println("[DEBUG] VBD: ", vbdData)
		log.Println("[DEBUG] VBD Type: ", vbd.Type)

		switch vbd.Type {
		case xenAPI.VbdTypeCD:
			cdrom = append(cdrom, vbdData)
			break
		case xenAPI.VbdTypeDisk:
			hdd = append(hdd, vbdData)
		default:
			return nil, nil, fmt.Errorf("Unsupported VBD type %q", string(vbd.Type))
		}
	}

	return hdd, cdrom, nil
}

func setSchemaVBDs(c *Connection, vm *VMDescriptor, d *schema.ResourceData) error {
	var err error
	var hdd []map[string]interface{}
	var cdrom []map[string]interface{}
	if hdd, cdrom, err = readVBDs(c, vm); err != nil {
		log.Println("[ERROR] ", err)
		return err
	}

	log.Println("[DEBUG] Found ", len(cdrom), " CDs and ", len(hdd), " HDDs")
	err = d.Set(vmSchemaHardDrive, hdd)
	if err != nil {
		log.Println("[ERROR] ", err)
		return err
	}
	err = d.Set(vmSchemaCdRom, cdrom)
	if err != nil {
		log.Println("[ERROR] ", err)
		return err
	}

	return nil
}

func createVBD(c *Connection, vbd *VBDDescriptor) (*VBDDescriptor, error) {
	log.Println(fmt.Sprintf("[DEBUG] Creating VBD for VM %q", vbd.VM.Name))

	vbdObject := xenAPI.VBDRecord{
		Type:       vbd.Type,
		Mode:       vbd.Mode,
		Bootable:   vbd.Bootable,
		VM:         vbd.VM.VMRef,
		Empty:      vbd.VDI == nil,
		Userdevice: vbd.UserDevice,
	}

	if devices, err := c.client.VM.GetAllowedVBDDevices(c.session, vbd.VM.VMRef); err == nil {
		if len(devices) == 0 {
			return nil, fmt.Errorf("No available devices to attach to")
		}
		vbdObject.Userdevice = devices[0]
		log.Println("[DEBUG] Selected device for VBD: ", vbdObject.Userdevice)
	} else {
		return nil, err
	}

	if vbd.VDI != nil {
		vbdObject.VDI = vbd.VDI.VDIRef
	}

	vbdRef, err := c.client.VBD.Create(c.session, vbdObject)
	if err != nil {
		return nil, err
	}

	log.Println(fmt.Sprintf("[DEBUG] Created VBD"))

	vbd.VBDRef = vbdRef
	err = vbd.Query(c)
	if err != nil {
		return nil, err
	}

	log.Println(fmt.Sprintf("[DEBUG] VBD  UUID %q", vbd.UUID))

	if vbd.VM.PowerState == xenAPI.VMPowerStateRunning {
		err = c.client.VBD.Plug(c.session, vbdRef)
		if err != nil {
			return nil, err
		}

		log.Println(fmt.Sprintf("[DEBUG] Plugged VBD %q to VM %q", vbd.UUID, vbd.VM.Name))
	}

	return vbd, nil
}

func vbdHash(v interface{}) int {
	m := v.(map[string]interface{})
	var buf bytes.Buffer
	var count int = 0
	var b int

	userDevice := m[vbdSchemaUserDevice].(string)
	isTemplateDevice := m[vbdSchemaTemplateDevice].(bool)
	mode := m[vbdSchemaMode].(string)
	bootable := m[vbdSchemaBootable].(bool)
	vdiUUID := m[vbdSchemaVdiUUID].(string)

	log.Println("[DEBUG] Calculating hash for ", v)

	if isTemplateDevice || userDevice != "" {
		b, _ = buf.WriteString(fmt.Sprintf("%s", userDevice))
		count += b
	}

	if !isTemplateDevice {
		b, _ = buf.WriteString(fmt.Sprintf("-%s", vdiUUID))
		count += b

		if mode != "" {
			b, _ = buf.WriteString(fmt.Sprintf("%-s", mode))
			count += b
		}

		b, _ = buf.WriteString(fmt.Sprintf("-%t", bootable))
		count += b
	}
	log.Println("Consumed total ", count, " bytes to generate hash")
	log.Println("String for hash: ", buf.String())

	return hashcode.String(buf.String())
}

func createVBDs(c *Connection, s []interface{}, vbdType xenAPI.VbdType, vm *VMDescriptor) (err error) {

	if err := readTemplateVBDsToSchema(c, vm, s, vbdType); err != nil {
		return err
	}

	log.Println("[DEBUG] Creating ", len(s), " VBDS of type ", vbdType)

	for _, schm := range s {
		data := schm.(map[string]interface{})

		if data[vbdSchemaUserDevice] != "" {
			continue
		}

		var vbd *VBDDescriptor
		var err error
		if vbd, err = readVBDFromSchema(c, data); err != nil {
			return err
		}

		// User Device is used to specify existing VBDs
		if vbd.UserDevice != "" {
			continue
		}

		vbd.Type = vbdType
		vbd.VM = vm

		if vbdType == xenAPI.VbdTypeCD {
			vbd.Mode = xenAPI.VbdModeRO
		}

		if vbd, err = createVBD(c, vbd); err != nil {
			return err
		}

		data[vbdSchemaUserDevice] = vbd.UserDevice
		data[vbdSchemaVdiUUID] = vbd.VDI.UUID
		data[vbdSchemaBootable] = vbd.Bootable
		data[vbdSchemaMode] = vbd.Mode
	}

	return nil
}

func resourceVBD() *schema.Resource {
	return &schema.Resource{

		Schema: map[string]*schema.Schema{
			vbdSchemaTemplateDevice: &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Default:  false,
			},
			vbdSchemaVdiUUID: &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
			vbdSchemaUserDevice: &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
			vbdSchemaBootable: &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Computed: true,
			},
			vbdSchemaMode: &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
		},
	}
}
<|endoftext|>