<commit_before>package pulse

import (
	"fmt"
	"log"
	"net/http"

	"github.com/garyburd/redigo/redis"
	"github.com/gorilla/mux"
	"github.com/polydice/pulse/messenger"
	"github.com/polydice/pulse/sns"
)

type Pump struct {
	msgers map[string]*messenger.Messenger
	port   string
}

func New(port string) *Pump {
	return &Pump{
		msgers: make(map[string]*messenger.Messenger),
		port:   port,
	}
}

func (self *Pump) RegisterMessenger(name string, msger *messenger.Messenger) {
	self.msgers[name] = msger
}

func (self *Pump) Start(allowCrossDomain bool) {

	r := mux.NewRouter()
	r.HandleFunc("/subscribe/{event}", func(resp http.ResponseWriter, req *http.Request) {
		vars := mux.Vars(req)
		event := vars["event"]

		self.runMsger(event, resp, allowCrossDomain)

		// Done.
		log.Println("Finished HTTP request at ", req.URL.Path)
	})

	r.HandleFunc("/subscribe/{event}/{id:[0-9]}+", func(resp http.ResponseWriter, req *http.Request) {
		vars := mux.Vars(req)
		event := vars["event"]
		eventId := vars["id"]
		msgerName := fmt.Sprintf("%v.%v", event, eventId)

		self.runMsger(msgerName, resp, allowCrossDomain)

		// Done.
		log.Println("Finished HTTP request at ", req.URL.Path)
	})

	r.HandleFunc("/publish/{event}", func(resp http.ResponseWriter, req *http.Request) {
		vars := mux.Vars(req)
		name := vars["event"]

		var msger *messenger.Messenger
		if self.msgers[name] == nil {
			msger = messenger.New(name)
			self.RegisterMessenger(name, msger)
			log.Println("SNS endpoint: listening /publish/" + msger.Name())
			log.Println("SSE endpoint: listening /subscribe/" + msger.Name())
		} else {
			msger = self.msgers[name]
		}

		n := sns.NewFromRequest(req)
		msger.SendMessage(n.Message)
	})

	log.Println("HTTP server port " + self.port)
	http.ListenAndServe(self.port, r)
}

func (self *Pump) runMsger(msgerName string, resp http.ResponseWriter, allowCrossDomain bool) {
	var msger *messenger.Messenger
	if self.msgers[msgerName] == nil {
		msger = messenger.New(msgerName)
		self.RegisterMessenger(msgerName, msger)
		// TODO: fix display format to subscribe/events/id
		log.Println("SSE endpoint: listening /subscribe/" + msger.Name())
	} else {
		msger = self.msgers[msgerName]
	}

	go self.subscribeRedis(msgerName, msger)

	// Make sure that the writer supports flushing.
	//
	f, ok := resp.(http.Flusher)
	if !ok {
		http.Error(resp, "Streaming unsupported", http.StatusInternalServerError)
		return
	}
	c, ok := resp.(http.CloseNotifier)
	if !ok {
		http.Error(resp, "Close notification unsupported", http.StatusInternalServerError)
		return
	}
	closer := c.CloseNotify()

	// Create a new channel, over which the broker can
	// send self client messages.
	messageChan := make(chan string)

	// Add self client to the map of those that should
	// receive updates
	msger.AddClient(messageChan)

	// Remove self client from the map of attached clients
	// when `EventHandler` exits.
	defer func() {
		msger.RemoveClient(messageChan)
	}()

	// Set the headers related to event streaming.
	resp.Header().Set("Content-Type", "text/event-stream")
	resp.Header().Set("Cache-Control", "no-cache")
	resp.Header().Set("Connection", "keep-alive")
	if allowCrossDomain {
		resp.Header().Set("Access-Control-Allow-Origin", "*")
	}

	// Use the CloseNotifier interface
	// https://code.google.com/p/go/source/detail?name=3292433291b2
	//
	// NOTE: we could loop endlessly; however, then you
	// could not easily detect clients that dettach and the
	// server would continue to send them messages long after
	// they're gone due to the "keep-alive" header.  One of
	// the nifty aspects of SSE is that clients automatically
	// reconnect when they lose their connection.
	//
	for {
		select {
		case msg := <-messageChan:
			fmt.Fprintf(resp, "data: %s\n\n", msg)
			f.Flush()
		case <-closer:
			log.Println("Closing connection")
			return
		}
	}
}

func (self *Pump) subscribeRedis(channel string, msger *messenger.Messenger) {

	c, err := redis.Dial("tcp", ":6379")
	if err != nil {
		panic(err)
	}
	defer c.Close()

	psc := redis.PubSubConn{c}
	psc.Subscribe(channel)
	for {
		switch v := psc.Receive().(type) {
		case redis.Message:
			fmt.Printf("PMessage: channel:%s data:%s\n", v.Channel, v.Data)
			msger.SendMessage(string(v.Data))
		case redis.Subscription:
			log.Printf("Subscription: kind:%s channel:%s count:%d\n", v.Kind, v.Channel, v.Count)
			if v.Count == 0 {
				return
			}
		case error:
			log.Printf("error: %v\n", v)
			return
		}
	}
}
<commit_msg>Allow specify redis address via env.<commit_after>package pulse

import (
	"fmt"
	"log"
	"net/http"
	"os"

	"github.com/garyburd/redigo/redis"
	"github.com/gorilla/mux"
	"github.com/polydice/pulse/messenger"
	"github.com/polydice/pulse/sns"
)

type Pump struct {
	msgers map[string]*messenger.Messenger
	port   string
}

func New(port string) *Pump {
	return &Pump{
		msgers: make(map[string]*messenger.Messenger),
		port:   port,
	}
}

func (self *Pump) RegisterMessenger(name string, msger *messenger.Messenger) {
	self.msgers[name] = msger
}

func (self *Pump) Start(allowCrossDomain bool) {

	r := mux.NewRouter()
	r.HandleFunc("/subscribe/{event}", func(resp http.ResponseWriter, req *http.Request) {
		vars := mux.Vars(req)
		event := vars["event"]

		self.runMsger(event, resp, allowCrossDomain)

		// Done.
		log.Println("Finished HTTP request at ", req.URL.Path)
	})

	r.HandleFunc("/subscribe/{event}/{id:[0-9]}+", func(resp http.ResponseWriter, req *http.Request) {
		vars := mux.Vars(req)
		event := vars["event"]
		eventId := vars["id"]
		msgerName := fmt.Sprintf("%v.%v", event, eventId)

		self.runMsger(msgerName, resp, allowCrossDomain)

		// Done.
		log.Println("Finished HTTP request at ", req.URL.Path)
	})

	r.HandleFunc("/publish/{event}", func(resp http.ResponseWriter, req *http.Request) {
		vars := mux.Vars(req)
		name := vars["event"]

		var msger *messenger.Messenger
		if self.msgers[name] == nil {
			msger = messenger.New(name)
			self.RegisterMessenger(name, msger)
			log.Println("SNS endpoint: listening /publish/" + msger.Name())
			log.Println("SSE endpoint: listening /subscribe/" + msger.Name())
		} else {
			msger = self.msgers[name]
		}

		n := sns.NewFromRequest(req)
		msger.SendMessage(n.Message)
	})

	log.Println("HTTP server port " + self.port)
	http.ListenAndServe(self.port, r)
}

func (self *Pump) runMsger(msgerName string, resp http.ResponseWriter, allowCrossDomain bool) {
	var msger *messenger.Messenger
	if self.msgers[msgerName] == nil {
		msger = messenger.New(msgerName)
		self.RegisterMessenger(msgerName, msger)
		// TODO: fix display format to subscribe/events/id
		log.Println("SSE endpoint: listening /subscribe/" + msger.Name())
	} else {
		msger = self.msgers[msgerName]
	}

	go self.subscribeRedis(msgerName, msger)

	// Make sure that the writer supports flushing.
	//
	f, ok := resp.(http.Flusher)
	if !ok {
		http.Error(resp, "Streaming unsupported", http.StatusInternalServerError)
		return
	}
	c, ok := resp.(http.CloseNotifier)
	if !ok {
		http.Error(resp, "Close notification unsupported", http.StatusInternalServerError)
		return
	}
	closer := c.CloseNotify()

	// Create a new channel, over which the broker can
	// send self client messages.
	messageChan := make(chan string)

	// Add self client to the map of those that should
	// receive updates
	msger.AddClient(messageChan)

	// Remove self client from the map of attached clients
	// when `EventHandler` exits.
	defer func() {
		msger.RemoveClient(messageChan)
	}()

	// Set the headers related to event streaming.
	resp.Header().Set("Content-Type", "text/event-stream")
	resp.Header().Set("Cache-Control", "no-cache")
	resp.Header().Set("Connection", "keep-alive")
	if allowCrossDomain {
		resp.Header().Set("Access-Control-Allow-Origin", "*")
	}

	// Use the CloseNotifier interface
	// https://code.google.com/p/go/source/detail?name=3292433291b2
	//
	// NOTE: we could loop endlessly; however, then you
	// could not easily detect clients that dettach and the
	// server would continue to send them messages long after
	// they're gone due to the "keep-alive" header.  One of
	// the nifty aspects of SSE is that clients automatically
	// reconnect when they lose their connection.
	//
	for {
		select {
		case msg := <-messageChan:
			fmt.Fprintf(resp, "data: %s\n\n", msg)
			f.Flush()
		case <-closer:
			log.Println("Closing connection")
			return
		}
	}
}

func (self *Pump) subscribeRedis(channel string, msger *messenger.Messenger) {

	c, err := redis.Dial("tcp", os.Getenv("REDIS_ADDR"))
	if err != nil {
		panic(err)
	}
	defer c.Close()

	psc := redis.PubSubConn{c}
	psc.Subscribe(channel)
	for {
		switch v := psc.Receive().(type) {
		case redis.Message:
			fmt.Printf("PMessage: channel:%s data:%s\n", v.Channel, v.Data)
			msger.SendMessage(string(v.Data))
		case redis.Subscription:
			log.Printf("Subscription: kind:%s channel:%s count:%d\n", v.Kind, v.Channel, v.Count)
			if v.Count == 0 {
				return
			}
		case error:
			log.Printf("error: %v\n", v)
			return
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"strings"
)

// Push command line tool
func cmdPush() int {

	// git-lob push [--all] [--recheck] [--force] [<remote> [<ref>...]]

	// Validate custom options
	errorList := validateCustomOptions(GlobalOptions, nil, []string{"all", "recheck", "force"})
	if len(errorList) > 0 {
		fmt.Fprintf(os.Stderr, strings.Join(errorList, "\n"))
		return 9
	}

	optAll := GlobalOptions.BoolOpts.Contains("all")
	optRecheck := GlobalOptions.BoolOpts.Contains("recheck")
	optForce := GlobalOptions.BoolOpts.Contains("force")

	// Determine remote
	var remoteName string
	var refspecs []*GitRefSpec
	if len(GlobalOptions.Args) > 0 {
		// first parameter must be remote if there are arguments
		remoteName = GlobalOptions.Args[0]

		// Remaining args are refspecs
		if len(GlobalOptions.Args) > 1 {
			// Not valid if --all
			if optAll {
				fmt.Fprintf(os.Stderr, "git-lob: Too many arguments; cannot include refspec when using --all\n")
				return 7
			}
			for _, arg := range GlobalOptions.Args[1:] {
				r := ParseGitRefSpec(arg)
				refspecs = append(refspecs, r)
			}
		}

	} else {
		remoteName = GetGitDefaultRemote()
	}

	// check the remote config to make sure it's valid
	provider, err := GetProviderForRemote(remoteName)
	if err != nil {
		fmt.Fprintf(os.Stderr, "git-lob: %v\n", err)
		return 7
	}

	if len(refspecs) == 0 {
		// No refspecs specified, so determine default
		if optAll {
			// TODO - enumerate all refs & include in refspecs
		} else {
			// TODO - determine refspec from current branch & push settings
		}
	}

	switch p := provider.(type) {
	case BasicSyncProvider:
		PushBasic(p, remoteName, refspecs, optForce, optRecheck)
	case SmartSyncProvider:
		PushSmart(p, remoteName, refspecs, optForce, optRecheck)
	}

	return 0
}

func PushBasic(provider BasicSyncProvider, remoteName string, refspecs []*GitRefSpec, force bool, recheck bool) {
	// TODO
}
func PushSmart(provider SmartSyncProvider, remoteName string, refspecs []*GitRefSpec, force bool, recheck bool) {
	// TODO
}

func cmdPushHelp() {
	fmt.Println(`Usage: git-lob push [options] [<remote> [<ref>...]]

  Uploads binaries to a remote, sending only binaries required to ensure
  that remote has the binary resources referenced at a set of commits.

  Behaves much like 'git push' except there are no destination refs, only
  supporting binary files.

Parameters:
  <remote>: The destination to upload to. This should correspond to the
            name of a remote (no direct URLs permitted) which is configured
            in .git/config. See REMOTES below for more details, additional
            config parameters are required in the remote.

            If no remote is specified, branch.*.remote configuration for the
            current branch is consulted to determine where to push. If the
            configuration is missing, it defaults to origin.
     <ref>: Which local reference(s) up to which we should make sure binaries
            are uploaded for. You can specify zero, one, or many local refs.
            There is no destination ref as in git push.

            If no ref is specified, and --all is not used, remote.*.push
            is consulted to see what to push, with push.default as a fallback.

            COMMIT RANGES

            You can also specify a range of refs in the form <ref1>..<ref2> to
            force git-lob push to check a specific range of commits for
            binaries, instead of using its own records of which commits it
            thinks are already up to date on this remote.
            See HISTORY CHECKING below.

Options:
  --all         Push all branches; cannot be used with other refs.
  --recheck     Re-check entire commit history to each ref instead of only
                back to last commit we believe is already pushed.
                See HISTORY CHECKING below for more details.
  --force       Always upload files even if the provider believes the file is
                already present on the remote. You shouldn't need this.
  --quiet, -q   Print less output
  --verbose, -v Print more output
  --dry-run     Don't actually delete anything, just report

REMOTES

Binaries in git-lob are not stored in the regular git repo, but a corresponding
binary store must always exist to provide the actual binary content. A remote
in git usually only gives you the real git repo, so git-lob needs to expand
the configuration parameters to git remotes to specify the location of the
corresponding remote binary store.

The <remote> parameter refers to a named remote in .git/config (plain URLs
cannot be supported). The remote entry itself is the same as any normal git
remote, except that it requires additional git-lob specific parameters. The
parameters depend on the type of binary storage ('provider') being used; see
'git-lob providers' for a list of available providers and
'git-lob provider <provider>' for specific details of one provider.

As an example, let's take the 'filesystem' provider, which simply uses the OS's
file system as a remote transport (obviously very simplistic):

[remote "origin"]
    # these 2 lines are standard git
    url = ssh://git@bitbucket.org/something/somthing.git
    fetch = +refs/heads/*:refs/remotes/origin/*
    # these next 2 lines are required to configure the remote binary store
    git-lob-provider = filesystem
    git-lob-path = /Volumes/shared/something/something/binary/store

Other providers may require other parameters. It's important to note that you
can share a binary store among multiple remote repos if you wish, much like
the local git-lob.sharedstore option, since binaries are stored by SHA.
Identical file content in multiple repos can be stored only once this way.
Of course, access control may be an issue to consider here though.

HISTORY CHECKING

When pushing binaries for a given ref, git-lob performs a search for commits
which reference git-lob binaries from that ref backwards, before checking
which of those binaries it needs to upload. This is so that we only upload
binaries that are actually referenced by the ref you're choosing to push,
and don't waste time on binaries in unpublished feature branches etc.

Because searching the whole of the git history can be slow on large
repositories, git-lob speeds this search up by keeping a record of which
commits it believes the remote already has all binaries for.

These records are updated whenever you git-lob push/pull. We do not use git's
own remote branch refs to track this, because pushing commits can be done
completely separately from binaries so we can't rely on that information.
So pushing and pulling branches in git has no effect on this state, only
git-lob push/pull.

If for some reason these records are wrong, and you need to push binaries
for a bigger range of commits, you can do this 2 ways:

1. Use the --recheck option. This is the 'nuclear option'; it will scan the
   entire history of the repo again to make 100% sure everything is correct.
   Can take a while on large repos.

2. Use a commit range for <ref>, i.e. <ref1>..<ref2>. git-lob will check that
   entire range of commits for binary references which will then be checked
   with the remote.

There are not many circumstances where you need to manually override the commit
range that is checked for binaries. Even if you edit commits, rebase etc,
git-lob should not miss any binaries, because the commit SHAs would change and
it would know to check any referenced binaries again. The main reason why
you would need to override the history checking is if the remote changed, for
example if someone manually deleted the remote binary store, or you moved to
a new URL without copying the data and needed to re-populate it from your local
repo.

`)
}
<commit_msg>Include some progress reporting in push command line<commit_after>package main

import (
	"fmt"
	"os"
	"strings"
)

// Push command line tool
func cmdPush() int {

	// git-lob push [--all] [--recheck] [--force] [<remote> [<ref>...]]

	// Validate custom options
	errorList := validateCustomOptions(GlobalOptions, nil, []string{"all", "recheck", "force"})
	if len(errorList) > 0 {
		fmt.Fprintf(os.Stderr, strings.Join(errorList, "\n"))
		return 9
	}

	optAll := GlobalOptions.BoolOpts.Contains("all")
	optRecheck := GlobalOptions.BoolOpts.Contains("recheck")
	optForce := GlobalOptions.BoolOpts.Contains("force")

	// Determine remote
	var remoteName string
	var refspecs []*GitRefSpec
	if len(GlobalOptions.Args) > 0 {
		// first parameter must be remote if there are arguments
		remoteName = GlobalOptions.Args[0]

		// Remaining args are refspecs
		if len(GlobalOptions.Args) > 1 {
			// Not valid if --all
			if optAll {
				fmt.Fprintf(os.Stderr, "git-lob: Too many arguments; cannot include refspec when using --all\n")
				return 7
			}
			for _, arg := range GlobalOptions.Args[1:] {
				r := ParseGitRefSpec(arg)
				refspecs = append(refspecs, r)
			}
		}

	} else {
		remoteName = GetGitDefaultRemote()
	}

	// check the remote config to make sure it's valid
	provider, err := GetProviderForRemote(remoteName)
	if err != nil {
		fmt.Fprintf(os.Stderr, "git-lob: %v\n", err)
		return 7
	}

	if len(refspecs) == 0 {
		// No refspecs specified, so determine default
		if optAll {
			// TODO - enumerate all refs & include in refspecs
		} else {
			// TODO - determine refspec from current branch & push settings
		}
	}

	progress := func(fileInProgress string, isSkipped bool, filepercent, overallpercent int, rate string) (abort bool) {
		// Note, no newline, just carriage return to overwrite single line in place
		if GlobalOptions.Verbose {
			// report file name too
			if filepercent == 100 {
				// in verbose mode we include newline after reporting file name at 100%
				if isSkipped {
					fmt.Printf("\rSkipped: %v (Up to date)\n", fileInProgress)
				} else {
					fmt.Printf("\rPushed: %v 100%%\n", fileInProgress)
				}
			} else {
				fmt.Printf("\rPushing: %v %d%%\tOverall: %d%%\t(%v)", fileInProgress, filepercent, overallpercent, rate)
			}
		} else {
			fmt.Printf("\rPushing: %d%%\t(%v)", overallpercent, rate)
		}

		return false

	}

	fmt.Println("Pushing to ", remoteName)

	switch p := provider.(type) {
	case BasicSyncProvider:
		PushBasic(p, remoteName, refspecs, optForce, optRecheck, progress)
	case SmartSyncProvider:
		PushSmart(p, remoteName, refspecs, optForce, optRecheck, progress)
	}

	// Because no newlines in progress reporting
	fmt.Println()

	return 0
}

func PushBasic(provider BasicSyncProvider, remoteName string, refspecs []*GitRefSpec, force bool, recheck bool,
	callback func(fileInProgress string, isSkipped bool, filepercent, overallpercent int, rate string) (abort bool)) {
	// TODO
}
func PushSmart(provider SmartSyncProvider, remoteName string, refspecs []*GitRefSpec, force bool, recheck bool,
	callback func(fileInProgress string, isSkipped bool, filepercent, overallpercent int, rate string) (abort bool)) {
	// TODO
}

func cmdPushHelp() {
	fmt.Println(`Usage: git-lob push [options] [<remote> [<ref>...]]

  Uploads binaries to a remote, sending only binaries required to ensure
  that remote has the binary resources referenced at a set of commits.

  Behaves much like 'git push' except there are no destination refs, only
  supporting binary files.

Parameters:
  <remote>: The destination to upload to. This should correspond to the
            name of a remote (no direct URLs permitted) which is configured
            in .git/config. See REMOTES below for more details, additional
            config parameters are required in the remote.

            If no remote is specified, branch.*.remote configuration for the
            current branch is consulted to determine where to push. If the
            configuration is missing, it defaults to origin.
     <ref>: Which local reference(s) up to which we should make sure binaries
            are uploaded for. You can specify zero, one, or many local refs.
            There is no destination ref as in git push.

            If no ref is specified, and --all is not used, remote.*.push
            is consulted to see what to push, with push.default as a fallback.

            COMMIT RANGES

            You can also specify a range of refs in the form <ref1>..<ref2> to
            force git-lob push to check a specific range of commits for
            binaries, instead of using its own records of which commits it
            thinks are already up to date on this remote.
            See HISTORY CHECKING below.

Options:
  --all         Push all branches; cannot be used with other refs.
  --recheck     Re-check entire commit history to each ref instead of only
                back to last commit we believe is already pushed.
                See HISTORY CHECKING below for more details.
  --force       Always upload files even if the provider believes the file is
                already present on the remote. You shouldn't need this.
  --quiet, -q   Print less output
  --verbose, -v Print more output
  --dry-run     Don't actually delete anything, just report

REMOTES

Binaries in git-lob are not stored in the regular git repo, but a corresponding
binary store must always exist to provide the actual binary content. A remote
in git usually only gives you the real git repo, so git-lob needs to expand
the configuration parameters to git remotes to specify the location of the
corresponding remote binary store.

The <remote> parameter refers to a named remote in .git/config (plain URLs
cannot be supported). The remote entry itself is the same as any normal git
remote, except that it requires additional git-lob specific parameters. The
parameters depend on the type of binary storage ('provider') being used; see
'git-lob providers' for a list of available providers and
'git-lob provider <provider>' for specific details of one provider.

As an example, let's take the 'filesystem' provider, which simply uses the OS's
file system as a remote transport (obviously very simplistic):

[remote "origin"]
    # these 2 lines are standard git
    url = ssh://git@bitbucket.org/something/somthing.git
    fetch = +refs/heads/*:refs/remotes/origin/*
    # these next 2 lines are required to configure the remote binary store
    git-lob-provider = filesystem
    git-lob-path = /Volumes/shared/something/something/binary/store

Other providers may require other parameters. It's important to note that you
can share a binary store among multiple remote repos if you wish, much like
the local git-lob.sharedstore option, since binaries are stored by SHA.
Identical file content in multiple repos can be stored only once this way.
Of course, access control may be an issue to consider here though.

HISTORY CHECKING

When pushing binaries for a given ref, git-lob performs a search for commits
which reference git-lob binaries from that ref backwards, before checking
which of those binaries it needs to upload. This is so that we only upload
binaries that are actually referenced by the ref you're choosing to push,
and don't waste time on binaries in unpublished feature branches etc.

Because searching the whole of the git history can be slow on large
repositories, git-lob speeds this search up by keeping a record of which
commits it believes the remote already has all binaries for.

These records are updated whenever you git-lob push/pull. We do not use git's
own remote branch refs to track this, because pushing commits can be done
completely separately from binaries so we can't rely on that information.
So pushing and pulling branches in git has no effect on this state, only
git-lob push/pull.

If for some reason these records are wrong, and you need to push binaries
for a bigger range of commits, you can do this 2 ways:

1. Use the --recheck option. This is the 'nuclear option'; it will scan the
   entire history of the repo again to make 100% sure everything is correct.
   Can take a while on large repos.

2. Use a commit range for <ref>, i.e. <ref1>..<ref2>. git-lob will check that
   entire range of commits for binary references which will then be checked
   with the remote.

There are not many circumstances where you need to manually override the commit
range that is checked for binaries. Even if you edit commits, rebase etc,
git-lob should not miss any binaries, because the commit SHAs would change and
it would know to check any referenced binaries again. The main reason why
you would need to override the history checking is if the remote changed, for
example if someone manually deleted the remote binary store, or you moved to
a new URL without copying the data and needed to re-populate it from your local
repo.

`)
}
<|endoftext|>


<commit_before>// Copyright (c) 2014-2016 Bitmark Inc.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package difficulty

import (
	"encoding/binary"
	"encoding/hex"
	"fmt"
	"github.com/bitmark-inc/bitmarkd/difficulty/filters"
	"github.com/bitmark-inc/bitmarkd/fault"
	"math"
	"math/big"
	"sync"
)

// the default values
const (
	OneUint64         uint64  = 0x00ffffffffffffff
	MinimumReciprocal float64 = 1.0
)

// number of block times to decay the difficulty by 50%
const HalfLife = 100

// the decay constant
const decayLambda float64 = math.Ln2 / HalfLife

// Type for difficulty
//
// bits is encoded as:
//    8 bit exponent,
//   57 bit mantissa normalised so msb is '1' and omitted
// mantissa is shifted by exponent+8
// examples:
//   the "One" value: 00 ff  ff ff  ff ff  ff ff
//   represents the 256 bit value: 00ff ffff ffff ffff 8000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
//   value: 01 ff  ff ff  ff ff  ff ff
//   represents the 256 bit value: 007f ffff ffff ffff c000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
type Difficulty struct {
	m *sync.RWMutex // pointer since MarshallJSON is pass by value

	big        big.Int // master value 256 bit integer expanded from bits
	reciprocal float64 // cache: floating point reciprocal difficulty
	bits       uint64  // cache: compat difficulty (encoded value)

	filter filters.Filter // filter for difficulty auto-adjust
}

// current difficulty
var Current = New()

// &Difficulty{
// 	m:      new(sync.RWMutex),
// 	filter: filters.NewCamm(MinimumReciprocal, 21, 41),
// }

// pool difficulty of 1 as 256 bit value
var constOne = []byte{
	0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}

// var constOne = []byte{
// 	0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
// 	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
// 	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
// 	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
// }

var one big.Int        // for reciprocal calculation
var floatOne big.Float // for reciprocal calculation

// on startup
func init() {
	one.SetBytes(constOne)
	floatOne.SetInt(&one)
	Current.SetBits(OneUint64)
}

// create a difficulty with the largest possible value
// which is the easiest for the miners and has the fewest leading zeros
func New() *Difficulty {
	d := new(Difficulty)
	return d.internalReset()
}

// Get 1/difficulty as normal floating-point value
// this is the Pdiff value
func (difficulty *Difficulty) Reciprocal() float64 {
	difficulty.m.RLock()
	defer difficulty.m.RUnlock()
	return difficulty.reciprocal
}

// Get difficulty as short packed value
func (difficulty *Difficulty) Bits() uint64 {
	difficulty.m.RLock()
	defer difficulty.m.RUnlock()
	return difficulty.bits
}

// Get difficulty as the big endian hex encodes short packed value
func (difficulty *Difficulty) String() string {
	difficulty.m.RLock()
	defer difficulty.m.RUnlock()
	return fmt.Sprintf("%016x", difficulty.bits)
}

// for the %#v format use 256 bit value
func (difficulty *Difficulty) GoString() string {
	return fmt.Sprintf("%064x", difficulty.BigInt())
}

// convert a uint64 difficulty value to a big.Int
func (difficulty *Difficulty) BigInt() *big.Int {
	difficulty.m.RLock()
	defer difficulty.m.RUnlock()
	d := new(big.Int)
	return d.Set(&difficulty.big)
}

// reset difficulty to minimum
// ensure write locked before calling this
func (difficulty *Difficulty) internalReset() *Difficulty {
	if nil == difficulty.m {
		difficulty.m = new(sync.RWMutex)
	}
	if nil == difficulty.filter {
		difficulty.filter = filters.NewCamm(MinimumReciprocal, 21, 41)
	}
	difficulty.big.Set(&one)
	difficulty.reciprocal = MinimumReciprocal
	difficulty.bits = OneUint64
	return difficulty
}

// set from a 64 bit word (bits)
func (difficulty *Difficulty) SetBits(u uint64) *Difficulty {

	// quick setup for default
	if OneUint64 == u {
		difficulty.m.Lock()
		defer difficulty.m.Unlock()
		return difficulty.internalReset()
	}

	exponent := (uint(u>>56) & 0xff)
	mantissa := (u & 0x00ffffffffffffff) | 0x0100000000000000 // include hidden bit

	// check for exponent overflow
	if exponent >= 0xc0 {
		fault.Criticalf("difficulty.SetBits(0x%16x) invalid value", u)
		fault.Panic("difficulty.SetBits: failed")
	}
	d := big.NewInt(0)
	d.SetUint64(mantissa)
	d.Lsh(d, 256-65-exponent) // account for hidden 56th bit

	// compute 1/d
	denominator := new(big.Float)
	denominator.SetInt(d)
	q := new(big.Float)
	result, _ := q.Quo(&floatOne, denominator).Float64()

	// modify cache
	difficulty.m.Lock()
	defer difficulty.m.Unlock()

	difficulty.big.Set(d)
	difficulty.reciprocal = result
	difficulty.bits = u

	return difficulty
}

func (difficulty *Difficulty) SetReciprocal(f float64) {
	difficulty.m.Lock()
	defer difficulty.m.Unlock()
	difficulty.internalSetReciprocal(f)
}

// ensure write locked before calling this
func (difficulty *Difficulty) internalSetReciprocal(f float64) float64 {
	if f < MinimumReciprocal {
		difficulty.internalReset()
		return difficulty.reciprocal
	}
	difficulty.reciprocal = f

	r := new(big.Float)
	r.SetMode(big.ToPositiveInf).SetPrec(256).SetFloat64(f).Quo(&floatOne, r)

	d, _ := r.Int(&difficulty.big)

	// fmt.Printf("f_1: %s\n", floatOne.Text('f', 80))
	// fmt.Printf("rec: %s\n", r.Text('f', 80))
	// fmt.Printf("big: %d\n", d)
	// fmt.Printf("%f\n big: %064x\n", f, d)
	// fmt.Printf("acc: %s\n", accuracy.String())

	buffer := d.Bytes() // no more than 32 bytes (256 bits)

	if len(buffer) > 32 {
		fault.Criticalf("difficulty.internalSetReciprocal(%g) invalid value", f)
		fault.Panic("difficulty.SetBits: failed - needs more than 256 bits")
	}

	// first non-zero byte will not exceed 0x7f as bigints are signed
	// but the above calculation results in an unsigned value
	// need to extract 56 bits with 1st bit as 1  and compute exponent
	for i, b := range buffer {
		if 0 != b {
			u := uint64(b) << 56
			if i+1 < len(buffer) {
				u |= uint64(buffer[i+1]) << 48
			}
			if i+2 < len(buffer) {
				u |= uint64(buffer[i+2]) << 40
			}
			if i+3 < len(buffer) {
				u |= uint64(buffer[i+3]) << 32
			}
			if i+4 < len(buffer) {
				u |= uint64(buffer[i+4]) << 24
			}
			if i+5 < len(buffer) {
				u |= uint64(buffer[i+5]) << 16
			}
			if i+6 < len(buffer) {
				u |= uint64(buffer[i+6]) << 8
			}
			if i+7 < len(buffer) {
				u |= uint64(buffer[i+7])
			}

			// compute exponent
			e := uint64(32-len(buffer)+i)*8 - 1

			// normalise
			rounder := 0
			for 0x0100000000000000 != 0xff00000000000000&u {
				if 1 == u&1 {
					rounder += 1
				}
				u >>= 1
				e -= 1
			}

			if rounder > 4 {
				u += 1
			}
			// hide 56th bit and incorporate exponent
			u = u&0x00ffffffffffffff | (e << 56)
			//fmt.Printf("bits: %016x\n", u)

			difficulty.bits = u
			break
		}
	}

	return difficulty.reciprocal
}

// set the difficulty from little endian bytes
func (difficulty *Difficulty) SetBytes(b []byte) *Difficulty {

	if len(b) != 8 {
		fault.Panicf("difficulty.SetBytes: too few bytes expeced 4 but had: %d", len(b))
	}

	u := uint64(b[0]) |
		uint64(b[1])<<8 |
		uint64(b[2])<<16 |
		uint64(b[3])<<24 |
		uint64(b[4])<<32 |
		uint64(b[5])<<40 |
		uint64(b[6])<<48 |
		uint64(b[7])<<56

	return difficulty.SetBits(u)
}

// adjustment based on error from desired cycle time
// call as difficulty.Adjust(expectedMinutes, actualMinutes)
func (difficulty *Difficulty) Adjust(expectedMinutes float64, actualMinutes float64) float64 {
	difficulty.m.Lock()
	defer difficulty.m.Unlock()

	// if k > 1 then difficulty is too low
	k := expectedMinutes / actualMinutes

	newReciprocal := k * difficulty.reciprocal

	// protect against underflow
	if newReciprocal < MinimumReciprocal {
		newReciprocal = MinimumReciprocal
	}

	// compute filter
	newReciprocal = difficulty.filter.Process(newReciprocal)

	// adjust difficulty
	return difficulty.internalSetReciprocal(newReciprocal)
}

// exponential decay of difficulty
// call each expected block period to decay the current difficulty
func (difficulty *Difficulty) Decay() float64 {
	difficulty.m.Lock()
	defer difficulty.m.Unlock()

	newReciprocal := difficulty.reciprocal - decayLambda*(difficulty.reciprocal)

	// adjust difficulty
	return difficulty.internalSetReciprocal(newReciprocal)
}

// convert a difficulty to little endian hex for JSON
func (difficulty Difficulty) MarshalJSON() ([]byte, error) {

	bits := make([]byte, 8)
	binary.LittleEndian.PutUint64(bits, difficulty.bits)

	size := 2 + hex.EncodedLen(len(bits))
	buffer := make([]byte, size)
	buffer[0] = '"'
	buffer[size-1] = '"'
	hex.Encode(buffer[1:], bits)
	return buffer, nil
}

// convert a difficulty little endian hex string to difficulty value
func (difficulty *Difficulty) UnmarshalJSON(s []byte) error {
	// length = '"' + characters + '"'
	last := len(s) - 1
	if '"' != s[0] || '"' != s[last] {
		return fault.ErrInvalidCharacter
	}

	b := s[1:last]

	buffer := make([]byte, hex.DecodedLen(len(b)))
	_, err := hex.Decode(buffer, b)
	if nil != err {
		return err
	}
	difficulty.internalReset()
	difficulty.SetBytes(buffer)
	return nil
}
<commit_msg>[difficulty] fix length check<commit_after>// Copyright (c) 2014-2016 Bitmark Inc.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package difficulty

import (
	"encoding/binary"
	"encoding/hex"
	"fmt"
	"github.com/bitmark-inc/bitmarkd/difficulty/filters"
	"github.com/bitmark-inc/bitmarkd/fault"
	"math"
	"math/big"
	"sync"
)

// the default values
const (
	OneUint64         uint64  = 0x00ffffffffffffff
	MinimumReciprocal float64 = 1.0
)

// number of block times to decay the difficulty by 50%
const HalfLife = 100

// the decay constant
const decayLambda float64 = math.Ln2 / HalfLife

// Type for difficulty
//
// bits is encoded as:
//    8 bit exponent,
//   57 bit mantissa normalised so msb is '1' and omitted
// mantissa is shifted by exponent+8
// examples:
//   the "One" value: 00 ff  ff ff  ff ff  ff ff
//   represents the 256 bit value: 00ff ffff ffff ffff 8000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
//   value: 01 ff  ff ff  ff ff  ff ff
//   represents the 256 bit value: 007f ffff ffff ffff c000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
type Difficulty struct {
	m *sync.RWMutex // pointer since MarshallJSON is pass by value

	big        big.Int // master value 256 bit integer expanded from bits
	reciprocal float64 // cache: floating point reciprocal difficulty
	bits       uint64  // cache: compat difficulty (encoded value)

	filter filters.Filter // filter for difficulty auto-adjust
}

// current difficulty
var Current = New()

// &Difficulty{
// 	m:      new(sync.RWMutex),
// 	filter: filters.NewCamm(MinimumReciprocal, 21, 41),
// }

// pool difficulty of 1 as 256 bit value
var constOne = []byte{
	0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}

// var constOne = []byte{
// 	0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
// 	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
// 	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
// 	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
// }

var one big.Int        // for reciprocal calculation
var floatOne big.Float // for reciprocal calculation

// on startup
func init() {
	one.SetBytes(constOne)
	floatOne.SetInt(&one)
	Current.SetBits(OneUint64)
}

// create a difficulty with the largest possible value
// which is the easiest for the miners and has the fewest leading zeros
func New() *Difficulty {
	d := new(Difficulty)
	return d.internalReset()
}

// Get 1/difficulty as normal floating-point value
// this is the Pdiff value
func (difficulty *Difficulty) Reciprocal() float64 {
	difficulty.m.RLock()
	defer difficulty.m.RUnlock()
	return difficulty.reciprocal
}

// Get difficulty as short packed value
func (difficulty *Difficulty) Bits() uint64 {
	difficulty.m.RLock()
	defer difficulty.m.RUnlock()
	return difficulty.bits
}

// Get difficulty as the big endian hex encodes short packed value
func (difficulty *Difficulty) String() string {
	difficulty.m.RLock()
	defer difficulty.m.RUnlock()
	return fmt.Sprintf("%016x", difficulty.bits)
}

// for the %#v format use 256 bit value
func (difficulty *Difficulty) GoString() string {
	return fmt.Sprintf("%064x", difficulty.BigInt())
}

// convert a uint64 difficulty value to a big.Int
func (difficulty *Difficulty) BigInt() *big.Int {
	difficulty.m.RLock()
	defer difficulty.m.RUnlock()
	d := new(big.Int)
	return d.Set(&difficulty.big)
}

// reset difficulty to minimum
// ensure write locked before calling this
func (difficulty *Difficulty) internalReset() *Difficulty {
	if nil == difficulty.m {
		difficulty.m = new(sync.RWMutex)
	}
	if nil == difficulty.filter {
		difficulty.filter = filters.NewCamm(MinimumReciprocal, 21, 41)
	}
	difficulty.big.Set(&one)
	difficulty.reciprocal = MinimumReciprocal
	difficulty.bits = OneUint64
	return difficulty
}

// set from a 64 bit word (bits)
func (difficulty *Difficulty) SetBits(u uint64) *Difficulty {

	// quick setup for default
	if OneUint64 == u {
		difficulty.m.Lock()
		defer difficulty.m.Unlock()
		return difficulty.internalReset()
	}

	exponent := (uint(u>>56) & 0xff)
	mantissa := (u & 0x00ffffffffffffff) | 0x0100000000000000 // include hidden bit

	// check for exponent overflow
	if exponent >= 0xc0 {
		fault.Criticalf("difficulty.SetBits(0x%16x) invalid value", u)
		fault.Panic("difficulty.SetBits: failed")
	}
	d := big.NewInt(0)
	d.SetUint64(mantissa)
	d.Lsh(d, 256-65-exponent) // account for hidden 56th bit

	// compute 1/d
	denominator := new(big.Float)
	denominator.SetInt(d)
	q := new(big.Float)
	result, _ := q.Quo(&floatOne, denominator).Float64()

	// modify cache
	difficulty.m.Lock()
	defer difficulty.m.Unlock()

	difficulty.big.Set(d)
	difficulty.reciprocal = result
	difficulty.bits = u

	return difficulty
}

func (difficulty *Difficulty) SetReciprocal(f float64) {
	difficulty.m.Lock()
	defer difficulty.m.Unlock()
	difficulty.internalSetReciprocal(f)
}

// ensure write locked before calling this
func (difficulty *Difficulty) internalSetReciprocal(f float64) float64 {
	if f < MinimumReciprocal {
		difficulty.internalReset()
		return difficulty.reciprocal
	}
	difficulty.reciprocal = f

	r := new(big.Float)
	r.SetMode(big.ToPositiveInf).SetPrec(256).SetFloat64(f).Quo(&floatOne, r)

	d, _ := r.Int(&difficulty.big)

	// fmt.Printf("f_1: %s\n", floatOne.Text('f', 80))
	// fmt.Printf("rec: %s\n", r.Text('f', 80))
	// fmt.Printf("big: %d\n", d)
	// fmt.Printf("%f\n big: %064x\n", f, d)
	// fmt.Printf("acc: %s\n", accuracy.String())

	buffer := d.Bytes() // no more than 32 bytes (256 bits)

	if len(buffer) > 32 {
		fault.Criticalf("difficulty.internalSetReciprocal(%g) invalid value", f)
		fault.Panic("difficulty.SetBits: failed - needs more than 256 bits")
	}

	// first non-zero byte will not exceed 0x7f as bigints are signed
	// but the above calculation results in an unsigned value
	// need to extract 56 bits with 1st bit as 1  and compute exponent
	for i, b := range buffer {
		if 0 != b {
			u := uint64(b) << 56
			if i+1 < len(buffer) {
				u |= uint64(buffer[i+1]) << 48
			}
			if i+2 < len(buffer) {
				u |= uint64(buffer[i+2]) << 40
			}
			if i+3 < len(buffer) {
				u |= uint64(buffer[i+3]) << 32
			}
			if i+4 < len(buffer) {
				u |= uint64(buffer[i+4]) << 24
			}
			if i+5 < len(buffer) {
				u |= uint64(buffer[i+5]) << 16
			}
			if i+6 < len(buffer) {
				u |= uint64(buffer[i+6]) << 8
			}
			if i+7 < len(buffer) {
				u |= uint64(buffer[i+7])
			}

			// compute exponent
			e := uint64(32-len(buffer)+i)*8 - 1

			// normalise
			rounder := 0
			for 0x0100000000000000 != 0xff00000000000000&u {
				if 1 == u&1 {
					rounder += 1
				}
				u >>= 1
				e -= 1
			}

			if rounder > 4 {
				u += 1
			}
			// hide 56th bit and incorporate exponent
			u = u&0x00ffffffffffffff | (e << 56)
			//fmt.Printf("bits: %016x\n", u)

			difficulty.bits = u
			break
		}
	}

	return difficulty.reciprocal
}

// set the difficulty from little endian bytes
func (difficulty *Difficulty) SetBytes(b []byte) *Difficulty {

	const byteLength = 8
	if len(b) != byteLength {
		fault.Panicf("difficulty.SetBytes: too few bytes expeced: %d had: %d", byteLength, len(b))
	}

	u := uint64(b[0]) |
		uint64(b[1])<<8 |
		uint64(b[2])<<16 |
		uint64(b[3])<<24 |
		uint64(b[4])<<32 |
		uint64(b[5])<<40 |
		uint64(b[6])<<48 |
		uint64(b[7])<<56

	return difficulty.SetBits(u)
}

// adjustment based on error from desired cycle time
// call as difficulty.Adjust(expectedMinutes, actualMinutes)
func (difficulty *Difficulty) Adjust(expectedMinutes float64, actualMinutes float64) float64 {
	difficulty.m.Lock()
	defer difficulty.m.Unlock()

	// if k > 1 then difficulty is too low
	k := expectedMinutes / actualMinutes

	newReciprocal := k * difficulty.reciprocal

	// protect against underflow
	if newReciprocal < MinimumReciprocal {
		newReciprocal = MinimumReciprocal
	}

	// compute filter
	newReciprocal = difficulty.filter.Process(newReciprocal)

	// adjust difficulty
	return difficulty.internalSetReciprocal(newReciprocal)
}

// exponential decay of difficulty
// call each expected block period to decay the current difficulty
func (difficulty *Difficulty) Decay() float64 {
	difficulty.m.Lock()
	defer difficulty.m.Unlock()

	newReciprocal := difficulty.reciprocal - decayLambda*(difficulty.reciprocal)

	// adjust difficulty
	return difficulty.internalSetReciprocal(newReciprocal)
}

// convert a difficulty to little endian hex for JSON
func (difficulty Difficulty) MarshalJSON() ([]byte, error) {

	bits := make([]byte, 8)
	binary.LittleEndian.PutUint64(bits, difficulty.bits)

	size := 2 + hex.EncodedLen(len(bits))
	buffer := make([]byte, size)
	buffer[0] = '"'
	buffer[size-1] = '"'
	hex.Encode(buffer[1:], bits)
	return buffer, nil
}

// convert a difficulty little endian hex string to difficulty value
func (difficulty *Difficulty) UnmarshalJSON(s []byte) error {
	// length = '"' + characters + '"'
	last := len(s) - 1
	if '"' != s[0] || '"' != s[last] {
		return fault.ErrInvalidCharacter
	}

	b := s[1:last]

	buffer := make([]byte, hex.DecodedLen(len(b)))
	_, err := hex.Decode(buffer, b)
	if nil != err {
		return err
	}
	difficulty.internalReset()
	difficulty.SetBytes(buffer)
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"log"

	"github.com/janicduplessis/resultscrawler/config"
	"github.com/janicduplessis/resultscrawler/crawler"
	"github.com/janicduplessis/resultscrawler/lib"
)

func main() {
	log.SetFlags(log.Lshortfile)

	envConfig := flag.Bool("useenv", false, "Use environnement variables config")
	flag.Parse()

	// Default config
	conf := &config.ServerConfig{
		ServerURL:  "localhost",
		ServerPort: "9898",
		DbName:     "resultscrawler",
		DbUser:     "resultscrawler",
		DbPassword: "***",
		DbURL:      "localhost",
		DbPort:     "7777",
	}

	if *envConfig {
		config.ReadEnv(conf)
	} else {
		config.ReadFile("crawler.json", conf)
	}

	config.ReadFile("crawler.json", conf)
	config.Print(conf)

	// Inject dependencies
	crypto := lib.NewCryptoHandler(config.Config.AESSecretKey)
	emailSender := &lib.EmailSender{}
	store := lib.NewMongoStore()

	userStore := lib.NewUserStoreHandler(store)

	crawlers := []*crawler.Crawler{
		crawler.NewCrawler(crypto),
	}
	scheduler := crawler.NewScheduler(crawlers, userStore, emailSender)

	log.Println("Server started")
	scheduler.Start()
	log.Println("Server stopped")
}
<commit_msg>fix env conf<commit_after>package main

import (
	"flag"
	"log"

	"github.com/janicduplessis/resultscrawler/config"
	"github.com/janicduplessis/resultscrawler/crawler"
	"github.com/janicduplessis/resultscrawler/lib"
)

func main() {
	log.SetFlags(log.Lshortfile)

	envConfig := flag.Bool("useenv", false, "Use environnement variables config")
	flag.Parse()

	// Default config
	conf := &config.ServerConfig{
		ServerURL:  "localhost",
		ServerPort: "9898",
		DbName:     "resultscrawler",
		DbUser:     "resultscrawler",
		DbPassword: "***",
		DbURL:      "localhost",
		DbPort:     "7777",
	}

	if *envConfig {
		config.ReadEnv(conf)
	} else {
		config.ReadFile("crawler.json", conf)
	}

	config.Print(conf)

	// Inject dependencies
	crypto := lib.NewCryptoHandler(config.Config.AESSecretKey)
	emailSender := &lib.EmailSender{}
	store := lib.NewMongoStore()

	userStore := lib.NewUserStoreHandler(store)

	crawlers := []*crawler.Crawler{
		crawler.NewCrawler(crypto),
	}
	scheduler := crawler.NewScheduler(crawlers, userStore, emailSender)

	log.Println("Server started")
	scheduler.Start()
	log.Println("Server stopped")
}
<|endoftext|>


<commit_before>package main

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

	"github.com/howeyc/fsnotify"
)

const (
	watchFlags = fsnotify.FSN_CREATE |
		fsnotify.FSN_DELETE |
		fsnotify.FSN_MODIFY
)

var (
	watcher  *fsnotify.Watcher
	watched  = make(map[string]struct{})
	exitCode = make(chan int)
	rootPath string

	emptyStruct = struct{}{}
	hasSuffix   = strings.HasSuffix
	contains    = strings.Contains
	sprintf     = fmt.Sprintf

	buildQueued = false
)

func panicIf(err error) {
	if err != nil {
		panic(err)
	}
}

func clearScrollBuffer() {
	print("\033c")
}

func build() {
	newCmd := func(name string, args ...string) *exec.Cmd {
		cmd := exec.Command(name, args...)
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		cmd.Dir = rootPath
		return cmd
	}

	runCmd := func(name string, args ...string) error {
		return newCmd(name, args...).Run()
	}

	{
		log.Println("glitch: formatting")
		if err := runCmd("go", "fmt", "./..."); err != nil {
			return
		}
	}

	{
		log.Println("glitch: building")
		if err := runCmd("go", "build", "./..."); err != nil {
			return
		}
	}

	{
		log.Println("glitch: build OK - vetting")
		if err := runCmd("go", "vet", "./..."); err != nil {
			return
		}
	}

	{
		log.Println("glitch: vet OK - testing")
		if err := runCmd("go", "test", "./..."); err != nil {
			return
		}
	}

	{
		log.Println("glitch: test OK - installing")
		if err := runCmd("go", "install"); err != nil {
			return
		}
	}

	log.Println("glitch: install OK - waiting for next build event")
}

func maybeQueueBuild(path string) {
	buildQueued = hasSuffix(path, ".go")
}

func handleCreate(path string) {
	watch(path)
	maybeQueueBuild(path)
}

func handleDelete(path string) {
	if _, watching := watched[path]; watching {
		_ = watcher.RemoveWatch(path)
		delete(watched, path)
	}
	maybeQueueBuild(path)
}

func handleModify(path string) {
	maybeQueueBuild(path)
}

func handleEvent(ev *fsnotify.FileEvent) {
	if len(ev.Name) > 0 {
		switch {
		case ev.IsCreate():
			handleCreate(ev.Name)
		case ev.IsDelete():
			handleDelete(ev.Name)
		case ev.IsModify():
			handleModify(ev.Name)
		}
	}
}

var (
	gitSuffix   = sprintf("%v.git", string(filepath.Separator))
	gitContains = sprintf("%v%v", gitSuffix, string(filepath.Separator))
)

func watch(dir string) {

	if _, watching := watched[dir]; watching {
		return
	}

	walker := func(path string, fileInfo os.FileInfo, err error) error {
		if err != nil {
			return nil
		}

		if hasSuffix(path, gitSuffix) || contains(path, gitContains) {
			return nil
		}

		if fileInfo.IsDir() {
			if err = watcher.WatchFlags(path, watchFlags); err == nil {
				watched[path] = emptyStruct
			}
		}

		return err
	}

	_ = filepath.Walk(dir, walker)
}

func periodicallyLogWatchedPaths() {
	logWatchedPaths := func() {
		log.Printf("glitch: watching: %v paths", len(watched))
		for path, _ := range watched {
			log.Println("glitch: watching:", path)
		}
	}

	logWatchedPaths()
	for _ = range time.Tick(5 * time.Second) {
		logWatchedPaths()
	}
}

func periodicallyLogBuildStatus() {
	logBuildStatus := func() {
		log.Println("glitch: buildQueued:", buildQueued)
	}

	logBuildStatus()
	for _ = range time.Tick(300 * time.Millisecond) {
		logBuildStatus()
	}
}

func runEventLoop() {
	for {
		select {
		case ev := <-watcher.Event:
			handleEvent(ev)
		case err := <-watcher.Error:
			panicIf(err)
		}
	}
}

func runBuildLoop() {
	consumeBuildQueue := func() {
		if buildQueued {
			buildQueued = false
			clearScrollBuffer()
			build()
		}
	}

	for _ = range time.Tick(1 * time.Second) {
		go consumeBuildQueue()
	}
}

func main() {
	wd, err := os.Getwd()
	panicIf(err)
	rootPath = wd

	w, err := fsnotify.NewWatcher()
	panicIf(err)
	watcher = w
	defer watcher.Close()

	//go periodicallyLogWatchedPaths()
	//go periodicallyLogBuildStatus()
	go runEventLoop()
	go runBuildLoop()

	watch(rootPath)
	os.Exit(<-exitCode)
}
<commit_msg>dropping fmt for now; too much friction with my editor (will discuss with Kyle :)<commit_after>package main

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

	"github.com/howeyc/fsnotify"
)

const (
	watchFlags = fsnotify.FSN_CREATE |
		fsnotify.FSN_DELETE |
		fsnotify.FSN_MODIFY
)

var (
	watcher  *fsnotify.Watcher
	watched  = make(map[string]struct{})
	exitCode = make(chan int)
	rootPath string

	emptyStruct = struct{}{}
	hasSuffix   = strings.HasSuffix
	contains    = strings.Contains
	sprintf     = fmt.Sprintf

	buildQueued = false
)

func panicIf(err error) {
	if err != nil {
		panic(err)
	}
}

func clearScrollBuffer() {
	print("\033c")
}

func build() {
	runCmd := func(name string, args ...string) error {
		cmd := exec.Command(name, args...)
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		cmd.Dir = rootPath
		return cmd.Run()
	}

	log.Println("glitch: building")
	if err := runCmd("go", "build", "./..."); err != nil {
		return
	}

	log.Println("glitch: build OK - vetting")
	if err := runCmd("go", "vet", "./..."); err != nil {
		return
	}

	log.Println("glitch: vet OK - testing")
	if err := runCmd("go", "test", "./..."); err != nil {
		return
	}

	log.Println("glitch: test OK - installing")
	if err := runCmd("go", "install"); err != nil {
		return
	}

	log.Println("glitch: install OK - waiting for next build event")
}

func maybeQueueBuild(path string) {
	buildQueued = hasSuffix(path, ".go")
}

func handleCreate(path string) {
	watch(path)
	maybeQueueBuild(path)
}

func handleDelete(path string) {
	if _, watching := watched[path]; watching {
		_ = watcher.RemoveWatch(path)
		delete(watched, path)
	}
	maybeQueueBuild(path)
}

func handleModify(path string) {
	maybeQueueBuild(path)
}

func handleEvent(ev *fsnotify.FileEvent) {
	if len(ev.Name) > 0 {
		switch {
		case ev.IsCreate():
			handleCreate(ev.Name)
		case ev.IsDelete():
			handleDelete(ev.Name)
		case ev.IsModify():
			handleModify(ev.Name)
		}
	}
}

var (
	gitSuffix   = sprintf("%v.git", string(filepath.Separator))
	gitContains = sprintf("%v%v", gitSuffix, string(filepath.Separator))
)

func watch(dir string) {

	if _, watching := watched[dir]; watching {
		return
	}

	walker := func(path string, fileInfo os.FileInfo, err error) error {
		if err != nil {
			return nil
		}

		if hasSuffix(path, gitSuffix) || contains(path, gitContains) {
			return nil
		}

		if fileInfo.IsDir() {
			if err = watcher.WatchFlags(path, watchFlags); err == nil {
				watched[path] = emptyStruct
			}
		}

		return err
	}

	_ = filepath.Walk(dir, walker)
}

func periodicallyLogWatchedPaths() {
	logWatchedPaths := func() {
		log.Printf("glitch: watching: %v paths", len(watched))
		for path, _ := range watched {
			log.Println("glitch: watching:", path)
		}
	}

	logWatchedPaths()
	for _ = range time.Tick(5 * time.Second) {
		logWatchedPaths()
	}
}

func periodicallyLogBuildStatus() {
	logBuildStatus := func() {
		log.Println("glitch: buildQueued:", buildQueued)
	}

	logBuildStatus()
	for _ = range time.Tick(300 * time.Millisecond) {
		logBuildStatus()
	}
}

func runEventLoop() {
	for {
		select {
		case ev := <-watcher.Event:
			handleEvent(ev)
		case err := <-watcher.Error:
			panicIf(err)
		}
	}
}

func runBuildLoop() {
	consumeBuildQueue := func() {
		if buildQueued {
			buildQueued = false
			clearScrollBuffer()
			build()
		}
	}

	for _ = range time.Tick(1 * time.Second) {
		go consumeBuildQueue()
	}
}

func main() {
	wd, err := os.Getwd()
	panicIf(err)
	rootPath = wd

	w, err := fsnotify.NewWatcher()
	panicIf(err)
	watcher = w
	defer watcher.Close()

	//go periodicallyLogWatchedPaths()
	//go periodicallyLogBuildStatus()
	go runEventLoop()
	go runBuildLoop()

	watch(rootPath)
	os.Exit(<-exitCode)
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/sha512"
	"encoding/hex"
	"encoding/json"
	"flag"
	"fmt"
	"github.com/vaughan0/go-ini"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strconv"

	//	"errors"
	//	"net"
)

//	defaults:
var (
	WORKERS   int64  = 10    //Number of workers
	IMGDIR    string = "img" //default download directory
	TAG       string = ""    //default string is empty, it can only ge extracted from command line
	STARTPAGE int    = 1     //default start page, derpiboo.ru 1-indexed
	STOPPAGE  int    = 0     //default stop page, would stop parsing json when stop page is reached or site reaches the end of search
	elog      *log.Logger
)

func main() {

	fmt.Println("Derpiboo.ru Downloader version 0.1.3")

	logfile, err := os.OpenFile("event.log", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) //file for putting errors into
	if err != nil {
		panic(err)
	}
	defer logfile.Close() //Almost forgot. Always close the file in the end.

	elog = log.New(io.MultiWriter(logfile, os.Stderr), "Errors at ", log.LstdFlags) //setting stuff for our logging: both errors and events.
	log.SetPrefix("Happens at ")
	log.SetFlags(log.LstdFlags)
	log.SetOutput(io.MultiWriter(logfile, os.Stdout))
	log.Println("Program start")

	config, err := ini.LoadFile("config.ini") // Loading default config file and checking for various errors.

	if os.IsNotExist(err) {
		elog.Fatalln("Config.ini does not exist, create it")
	}

	if err != nil {
		elog.Panicln(err)
	}

	//Getting stuff from config, overwriting defaults

	key, ok := config.Get("main", "key")

	if !ok {
		elog.Println("'key' variable missing from 'main' section")
	}

	W_temp, _ := config.Get("main", "workers")

	if W_temp != "" {
		WORKERS, err = strconv.ParseInt(W_temp, 10, 0)

		if err != nil {
			elog.Fatalln("Wrong configuration: Amount of workers is not a number")

		}
	}

	ID_temp, _ := config.Get("main", "downdir")

	if ID_temp != "" {
		IMGDIR = ID_temp
	}

	//Here we are parsing all the flags

	flag.StringVar(&TAG, "t", TAG, "Tags to download")
	flag.IntVar(&STARTPAGE, "p", STARTPAGE, "Starting page for search")
	flag.IntVar(&STOPPAGE, "sp", STOPPAGE, "Stopping page for search, 0 - parse all all search pages")

	flag.Parse()

	if flag.NArg() == 0 && TAG == "" { //If no arguments after flags and empty/unchanged tag, what we should download? Sane end of line.
		log.SetPrefix("Done at ")
		log.Println("Nothing to download, bye!")
		os.Exit(0)
	}

	//	creating directory for downloads if not yet done
	if err := os.MkdirAll(IMGDIR, 0777); err != nil { //Execute? No need to execute any image
		elog.Fatalln(err) //We can not create folder for images, dying horribly
	}

	//	creating channels to pass info to downloader and to signal job well done
	imgdat := make(chan Image, WORKERS)
	done := make(chan bool)

	if TAG == "" { //Because we can put imgid with flags. Why not?

		//	checking argument for being a number and then getting image data

		imgid := flag.Arg(0)
		_, err = strconv.Atoi(imgid)

		if err != nil {
			elog.Fatalln("Wrong input: can not parse", imgid, "as a number")
		}

		log.Println("Processing image No", imgid)

		go parseImg(imgdat, imgid, key)

	} else {

		//	and here we send tags to getter/parser

		log.Println("Processing tags", TAG)
		go parseTag(imgdat, TAG, key)
	}

	log.Println("Starting worker")
	go dlimage(imgdat, done)

	<-done
	log.SetPrefix("Done at ")
	log.Println("Finised")

}

type Image struct {
	imgid    int
	url      string
	filename string
	hash     string
}

func parseImg(imgchan chan<- Image, imgid string, key string) {

	source := "http://derpiboo.ru/" + imgid + ".json?nofav=&nocomments="
	if key != "" {
		source = source + "&key=" + key
	}

	fmt.Println("Getting image info at:", source)

	resp, err := http.Get(source) //Getting our nice http response. Needs checking for 404 and other responses that are... less expected
	if err != nil {
		elog.Println(err)
		return
	}

	defer resp.Body.Close() //and not forgetting to close it when it's done

	var dat map[string]interface{}

	body, err := ioutil.ReadAll(resp.Body) //stolen from official documentation
	if err != nil {
		elog.Println(err)
		return
	}

	if err := json.Unmarshal(body, &dat); //transforming json into native map

	err != nil {
		elog.Println(err)
		return
	}

	InfoToChannel(dat, imgchan)

	close(imgchan) //closing channel, we are done here

	return
}

func dlimage(imgchan <-chan Image, done chan bool) {

	fmt.Println("Worker started; reading channel") //nice notification that we are not forgotten

	for {

		imgdata, more := <-imgchan

		if more { //checking that there is an image in channel

			if imgdata.filename == "" {
				elog.Println("Empty filename. Oops?") //something somewhere had gone wrong, going to the next image
			} else {

				fmt.Println("Saving as", imgdata.filename)

				func() { // to not hold all the files open when there is no need

					output, err := os.Create(IMGDIR + string(os.PathSeparator) + imgdata.filename) //And now, THE FILE!
					if err != err {
						elog.Println("Error when creating file for image" + strconv.Itoa(imgdata.imgid))
						elog.Println(err)
						return
					}
					defer output.Close() //Not forgetting to deal with it after completing download

					response, err := http.Get(imgdata.url)
					if err != nil {
						elog.Println("Error when gettint image", imgdata.imgid)
						elog.Println(err)
						return
					}
					defer response.Body.Close() //Same, we shall not listen to the void when we finished getting image

					hash := sha512.New()

					io.Copy(io.MultiWriter(output, hash), response.Body) //	Writing things we got from Derpibooru into the

					b := make([]byte, hash.Size())
					hash.Sum(b[:0])

					//	fmt.Println("\n", hex.EncodeToString(b), "\n", imgdata.hash )

					if hex.EncodeToString(b) != imgdata.hash {
						elog.Println("Hash wrong with imageid", imgdata.imgid)
					}
				}()
			}

			//fmt.Println("\n", hex.EncodeToString(hash.Sum(nil)), "\n", imgdata.hash )

		} else {
			done <- true //well, there is no images in channel, it means we got them all, so synchronization is kicking in and ending the process
			break        //Just in case, so it would not stupidly die when program finishes - it will die smartly

		}
	}
}

func parseTag(imgchan chan<- Image, tag string, key string) {

	source := "http://derpiboo.ru/search.json?nofav=&nocomments=" //yay hardwiring url strings!

	if key != "" {
		source = source + "&key=" + key
	}

	fmt.Println("Searching as", source+"&q="+tag)
	var i int = 1

	for {
		func() {
			fmt.Println("Searching page", i)
			resp, err := http.Get(source + "&q=" + tag + "&page=" + strconv.Itoa(i)) //Getting our nice http response. Needs checking for 404 and other responses that are... less expected
			defer resp.Body.Close()                                                  //and not forgetting to close it when it's done. And before we panic and die horribly.
			if err != nil {
				elog.Println("Error while getting search page", i)
				elog.Println(err)
				return
			}

			var dats []map[string]interface{} //Because we got array incoming instead of single object, we using an slive of maps!

			//fmt.Println(resp)

			body, err := ioutil.ReadAll(resp.Body) //stolen from official documentation
			if err != nil {
				elog.Println("Error while reading search page", i)
				elog.Println(err)
				return
			}

			//fmt.Println(body)

			if err := json.Unmarshal(body, &dats); //transforming json into native slice of maps

			err != nil {
				elog.Println("Error while parsing search page", i)
				elog.Println(err)
				return

			}

			if len(dats) == 0 {
				fmt.Println("Pages are over")
				return
			} //exit due to finishing all pages

			for _, dat := range dats {
				InfoToChannel(dat, imgchan)
			}
			i++

		}()
	}

	close(imgchan)
}

func InfoToChannel(dat map[string]interface{}, imgchan chan<- Image) {

	var imgdata Image

	imgdata.url = "http:" + dat["image"].(string)
	imgdata.hash = dat["sha512_hash"].(string)
	imgdata.filename = (strconv.FormatFloat(dat["id_number"].(float64), 'f', -1, 64) + "." + dat["file_name"].(string) + "." + dat["original_format"].(string))
	imgdata.imgid = int(dat["id_number"].(float64))

	//	for troubleshooting later
	//	fmt.Println(dat)
	//	fmt.Println(imgdata.url)
	//	fmt.Println(imgdata.hash)
	//	fmt.Println(imgdata.filename)

	imgchan <- imgdata
}
<commit_msg>Emergency bugfix<commit_after>package main

import (
	"crypto/sha512"
	"encoding/hex"
	"encoding/json"
	"flag"
	"fmt"
	"github.com/vaughan0/go-ini"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strconv"

	//	"errors"
	//	"net"
)

//	defaults:
var (
	WORKERS   int64  = 10    //Number of workers
	IMGDIR    string = "img" //default download directory
	TAG       string = ""    //default string is empty, it can only ge extracted from command line
	STARTPAGE int    = 1     //default start page, derpiboo.ru 1-indexed
	STOPPAGE  int    = 0     //default stop page, would stop parsing json when stop page is reached or site reaches the end of search
	elog      *log.Logger
)

func main() {

	fmt.Println("Derpiboo.ru Downloader version 0.1.3")

	logfile, err := os.OpenFile("event.log", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) //file for putting errors into
	if err != nil {
		panic(err)
	}
	defer logfile.Close() //Almost forgot. Always close the file in the end.

	elog = log.New(io.MultiWriter(logfile, os.Stderr), "Errors at ", log.LstdFlags) //setting stuff for our logging: both errors and events.
	log.SetPrefix("Happens at ")
	log.SetFlags(log.LstdFlags)
	log.SetOutput(io.MultiWriter(logfile, os.Stdout))
	log.Println("Program start")

	config, err := ini.LoadFile("config.ini") // Loading default config file and checking for various errors.

	if os.IsNotExist(err) {
		elog.Fatalln("Config.ini does not exist, create it")
	}

	if err != nil {
		elog.Panicln(err)
	}

	//Getting stuff from config, overwriting defaults

	key, ok := config.Get("main", "key")

	if !ok {
		elog.Println("'key' variable missing from 'main' section")
	}

	W_temp, _ := config.Get("main", "workers")

	if W_temp != "" {
		WORKERS, err = strconv.ParseInt(W_temp, 10, 0)

		if err != nil {
			elog.Fatalln("Wrong configuration: Amount of workers is not a number")

		}
	}

	ID_temp, _ := config.Get("main", "downdir")

	if ID_temp != "" {
		IMGDIR = ID_temp
	}

	//Here we are parsing all the flags

	flag.StringVar(&TAG, "t", TAG, "Tags to download")
	flag.IntVar(&STARTPAGE, "p", STARTPAGE, "Starting page for search")
	flag.IntVar(&STOPPAGE, "sp", STOPPAGE, "Stopping page for search, 0 - parse all all search pages")

	flag.Parse()

	if flag.NArg() == 0 && TAG == "" { //If no arguments after flags and empty/unchanged tag, what we should download? Sane end of line.
		log.SetPrefix("Done at ")
		log.Println("Nothing to download, bye!")
		os.Exit(0)
	}

	//	creating directory for downloads if not yet done
	if err := os.MkdirAll(IMGDIR, 0777); err != nil { //Execute? No need to execute any image
		elog.Fatalln(err) //We can not create folder for images, dying horribly
	}

	//	creating channels to pass info to downloader and to signal job well done
	imgdat := make(chan Image, WORKERS)
	done := make(chan bool)

	if TAG == "" { //Because we can put imgid with flags. Why not?

		//	checking argument for being a number and then getting image data

		imgid := flag.Arg(0)
		_, err = strconv.Atoi(imgid)

		if err != nil {
			elog.Fatalln("Wrong input: can not parse", imgid, "as a number")
		}

		log.Println("Processing image No", imgid)

		go parseImg(imgdat, imgid, key)

	} else {

		//	and here we send tags to getter/parser

		log.Println("Processing tags", TAG)
		go parseTag(imgdat, TAG, key)
	}

	log.Println("Starting worker")
	go dlimage(imgdat, done)

	<-done
	log.SetPrefix("Done at ")
	log.Println("Finised")

}

type Image struct {
	imgid    int
	url      string
	filename string
	hash     string
}

func parseImg(imgchan chan<- Image, imgid string, key string) {

	source := "http://derpiboo.ru/" + imgid + ".json?nofav=&nocomments="
	if key != "" {
		source = source + "&key=" + key
	}

	fmt.Println("Getting image info at:", source)

	resp, err := http.Get(source) //Getting our nice http response. Needs checking for 404 and other responses that are... less expected
	if err != nil {
		elog.Println(err)
		return
	}

	defer resp.Body.Close() //and not forgetting to close it when it's done

	var dat map[string]interface{}

	body, err := ioutil.ReadAll(resp.Body) //stolen from official documentation
	if err != nil {
		elog.Println(err)
		return
	}

	if err := json.Unmarshal(body, &dat); //transforming json into native map

	err != nil {
		elog.Println(err)
		return
	}

	InfoToChannel(dat, imgchan)

	close(imgchan) //closing channel, we are done here

	return
}

func dlimage(imgchan <-chan Image, done chan bool) {

	fmt.Println("Worker started; reading channel") //nice notification that we are not forgotten

	for {

		imgdata, more := <-imgchan

		if more { //checking that there is an image in channel

			if imgdata.filename == "" {
				elog.Println("Empty filename. Oops?") //something somewhere had gone wrong, going to the next image
			} else {

				fmt.Println("Saving as", imgdata.filename)

				func() { // to not hold all the files open when there is no need

					output, err := os.Create(IMGDIR + string(os.PathSeparator) + imgdata.filename) //And now, THE FILE!
					if err != err {
						elog.Println("Error when creating file for image" + strconv.Itoa(imgdata.imgid))
						elog.Println(err)
						return
					}
					defer output.Close() //Not forgetting to deal with it after completing download

					response, err := http.Get(imgdata.url)
					if err != nil {
						elog.Println("Error when gettint image", imgdata.imgid)
						elog.Println(err)
						return
					}
					defer response.Body.Close() //Same, we shall not listen to the void when we finished getting image

					hash := sha512.New()

					io.Copy(io.MultiWriter(output, hash), response.Body) //	Writing things we got from Derpibooru into the

					b := make([]byte, hash.Size())
					hash.Sum(b[:0])

					//	fmt.Println("\n", hex.EncodeToString(b), "\n", imgdata.hash )

					if hex.EncodeToString(b) != imgdata.hash {
						elog.Println("Hash wrong with imageid", imgdata.imgid)
					}
				}()
			}

			//fmt.Println("\n", hex.EncodeToString(hash.Sum(nil)), "\n", imgdata.hash )

		} else {
			done <- true //well, there is no images in channel, it means we got them all, so synchronization is kicking in and ending the process
			break        //Just in case, so it would not stupidly die when program finishes - it will die smartly

		}
	}
}

func parseTag(imgchan chan<- Image, tag string, key string) {

	source := "http://derpiboo.ru/search.json?nofav=&nocomments=" //yay hardwiring url strings!

	if key != "" {
		source = source + "&key=" + key
	}

	fmt.Println("Searching as", source+"&q="+tag)
	var i int = 1
	var working bool = true
	for working {
		func() {
			fmt.Println("Searching page", i)
			resp, err := http.Get(source + "&q=" + tag + "&page=" + strconv.Itoa(i)) //Getting our nice http response. Needs checking for 404 and other responses that are... less expected
			defer resp.Body.Close()                                                  //and not forgetting to close it when it's done. And before we panic and die horribly.
			if err != nil {
				elog.Println("Error while getting search page", i)
				elog.Println(err)
				return
			}

			var dats []map[string]interface{} //Because we got array incoming instead of single object, we using an slive of maps!

			//fmt.Println(resp)

			body, err := ioutil.ReadAll(resp.Body) //stolen from official documentation
			if err != nil {
				elog.Println("Error while reading search page", i)
				elog.Println(err)
				return
			}

			//fmt.Println(body)

			if err := json.Unmarshal(body, &dats); //transforming json into native slice of maps

			err != nil {
				elog.Println("Error while parsing search page", i)
				elog.Println(err)
				return

			}

			if len(dats) == 0 {
				fmt.Println("Pages are over")
				working = false
				return
			} //exit due to finishing all pages

			for _, dat := range dats {
				InfoToChannel(dat, imgchan)
			}
			i++

		}()
	}

	close(imgchan)
}

func InfoToChannel(dat map[string]interface{}, imgchan chan<- Image) {

	var imgdata Image

	imgdata.url = "http:" + dat["image"].(string)
	imgdata.hash = dat["sha512_hash"].(string)
	imgdata.filename = (strconv.FormatFloat(dat["id_number"].(float64), 'f', -1, 64) + "." + dat["file_name"].(string) + "." + dat["original_format"].(string))
	imgdata.imgid = int(dat["id_number"].(float64))

	//	for troubleshooting later
	//	fmt.Println(dat)
	//	fmt.Println(imgdata.url)
	//	fmt.Println(imgdata.hash)
	//	fmt.Println(imgdata.filename)

	imgchan <- imgdata
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"math"
	"os"
	"os/signal"
	"strings"
	"time"

	"github.com/bcurren/go-hue"
	"github.com/bitly/go-simplejson"
	"github.com/davecgh/go-spew/spew"
	"github.com/ninjasphere/go-ninja"
)

var _ = fmt.Printf

type Light struct {
	Id            string
	Name          string
	Bus           *ninja.DeviceBus
	OnOffBus      *ninja.ChannelBus
	colorBus      *ninja.ChannelBus
	brightnessBus *ninja.ChannelBus
	Bridge        *hue.Bridge
	User          *hue.User
	LightState    *hue.LightState
	Batch         bool
	batchBus      *ninja.ChannelBus
	//bulbMonitor   *bulbmonitor.BulbMonitor
}

//--------------------------------------------------[HUE BASESTATION]----------------------------------------------------------------------

func getBridge() *hue.Bridge {
	nobridge := true
	var allbridges []*hue.Bridge
	for nobridge {
		allbridges, _ = hue.FindBridges()
		if len(allbridges) == 0 {
			log.Printf("Couldn't find bridge, retrying")
			time.Sleep(time.Second * 2) //this sucks
		} else {
			nobridge = false
			log.Printf("got %d bridges: %s", len(allbridges), allbridges)
		}
	}
	return allbridges[0]
}

func getUser(bridge *hue.Bridge) *hue.User {
	var user *hue.User
	var err error
	noUser := true
	retries := 0
	serial, err := ninja.GetSerial()
	if err != nil {
		log.Fatalf("Problem determining if hue user is valid: %s", err)
	}
	username := serial + serial //username must be long 10-40 characters
	isvaliduser, err := bridge.IsValidUser(username)
	if err != nil {
		log.Printf("Problem determining if hue user is valid")
	}

	if isvaliduser {
		user = hue.NewUserWithBridge(username, bridge)
	} else {
		for noUser {
			user, err = bridge.CreateUser("ninjadevice", username)
			if err != nil {
				if strings.Contains(err.Error(), "101") { // there's probably a nicer way to check this
					retries++
					log.Printf("Couldn't make user, push link button. Retry: %d", retries)
					time.Sleep(time.Second * 2) //this sucks
				} else {
					log.Fatalf("Error creating user: %s", err)
				}
			}

			if user != nil {
				noUser = false
			}
		}
	}
	return user
}

//--------------------------------------------------[BUSES]----------------------------------------------------------------------

func getOnOffBus(light *Light) *ninja.ChannelBus {
	methods := []string{"turnOn", "turnOff", "set"}
	events := []string{"state"}
	onOffBus, _ := light.Bus.AnnounceChannel("on-off", "on-off", methods, events, func(method string, payload *simplejson.Json) {
		if light.Batch == true {
			return
		}
		switch method {
		case "turnOn":
			light.sendLightState(light.turnOnOff(true))
		case "turnOff":
			light.sendLightState(light.turnOnOff(false))
		case "set":
			state, _ := payload.GetIndex(0).Bool()
			light.sendLightState(light.turnOnOff(state))
		default:
			log.Printf("On-off got an unknown method %s", method)
			return
		}
	})

	return onOffBus
}

func getBrightBus(light *Light) *ninja.ChannelBus {
	methods := []string{"set"}
	events := []string{"state"}
	brightnessBus, _ := light.Bus.AnnounceChannel("brightness", "brightness", methods, events, func(method string, payload *simplejson.Json) {
		if light.Batch == true {
			return
		}

		switch method {
		case "set":
			brightness, _ := payload.GetIndex(0).Float64()
			log.Printf("Setting brightness to %f!", brightness)
			light.sendLightState(light.setBrightness(brightness))
		default:
			log.Printf("Brightness got an unknown method %s", method)
			return
		}

	})

	return brightnessBus

}

func getColorBus(light *Light) *ninja.ChannelBus {
	methods := []string{"set"}
	events := []string{"state"}
	colorBus, _ := light.Bus.AnnounceChannel("color", "color", methods, events, func(method string, payload *simplejson.Json) {
		if light.Batch == true {
			return
		}
		switch method {
		case "set":
			log.Printf("Setting color to %s!", payload)
			light.sendLightState(light.setColor(payload))
		default:
			log.Printf("Color got an unknown method %s", method)
		}
	})

	return colorBus
}

func getBatchBus(light *Light) *ninja.ChannelBus {
	methods := []string{"setBatch"}
	events := []string{"state"}
	batchBus, _ := light.Bus.AnnounceChannel("core.batching", "core.batching", methods, events, func(method string, payload *simplejson.Json) {
		switch method {
		case "setBatch":
			light.setBatchColor(payload.GetIndex(0))
		default:
			log.Printf("Color got an unknown method %s", method)
			return
		}
	})

	return batchBus
}

func NewLight(l *hue.Light, bus *ninja.DriverBus, bridge *hue.Bridge, user *hue.User) (*Light, error) { //TODO cut this down!

	lightState := createLightState()

	light := &Light{
		Id:         l.Id,
		Name:       l.Name,
		Bridge:     bridge,
		User:       user,
		LightState: lightState,
		Batch:      false,
	}

	sigs, _ := simplejson.NewJson([]byte(`{
			"ninja:manufacturer": "Phillips",
			"ninja:productName": "Hue",
			"manufacturer:productModelId": "",
			"ninja:productType": "Light",
			"ninja:thingType": "light"
	}`))

	la, _ := user.GetLightAttributes(l.Id)
	sigs.Set("manufacturer:productModelId", la.ModelId)

	deviceBus, _ := bus.AnnounceDevice(l.Id, "hue", l.Name, sigs)
	light.Bus = deviceBus
	light.OnOffBus = getOnOffBus(light)
	light.brightnessBus = getBrightBus(light)
	light.colorBus = getColorBus(light)
	light.batchBus = getBatchBus(light)

	return light, nil
}

func (l Light) turnOnOff(state bool) *hue.LightState {
	lightState := &hue.LightState{
		On: &state,
	}
	return lightState

}

func (l Light) setBrightness(fbrightness float64) *hue.LightState {
	brightness := uint8(fbrightness * math.MaxUint8)
	lightState := &hue.LightState{
		Brightness: &brightness,
	}
	return lightState
}

func (l Light) setColor(payload *simplejson.Json) *hue.LightState {

	var lightState *hue.LightState
	colorpayload := payload.Get("color")
	mode, err := colorpayload.Get("mode").String()
	if err != nil {
		log.Printf("No mode sent to color bus: %s", err)
		spew.Dump(payload)
	}

	switch mode {
	case "hue": //TODO less verbose plz
		fhue, err := colorpayload.Get("hue").Float64()
		if err != nil {
			log.Printf("No hue sent to color bus: %s", err)
			spew.Dump(payload)
		}
		uint16hue := uint16(fhue * math.MaxUint16)
		fsaturation, err := colorpayload.Get("saturation").Float64()
		if err != nil {
			log.Printf("No saturation sent to color bus: %s", err)
			spew.Dump(payload)
		}
		saturation := uint8(fsaturation * math.MaxUint8)
		lightState = &hue.LightState{
			Hue:        &uint16hue,
			Saturation: &saturation,
		}

	case "xy":
		x, err := colorpayload.Get("x").Float64()
		if err != nil {
			log.Printf("X coord not sent to color bus: %s", err)
			spew.Dump(payload)
		}
		y, err := colorpayload.Get("y").Float64()
		if err != nil {
			log.Printf("Y coord not sent to color bus: %s", err)
			spew.Dump(payload)
		}
		xy := []float64{x, y}
		lightState = &hue.LightState{
			XY: xy,
		}
	case "temperature":
		log.Printf("got temp")
		temp, err := colorpayload.Get("temperature").Float64()
		if err != nil {
			log.Printf("No temperature sent to color bus: %s", err)
			spew.Dump(payload)
		}
		utemp := uint16(math.Floor(1000000 / temp))
		lightState = &hue.LightState{
			ColorTemp: &utemp,
		}

	default:
		log.Printf("Bad color mode: %s", mode)
		return nil
	}

	if trans, e := payload.Get("transition").Int(); e == nil {
		st := l.setTransition(trans)
		lightState.TransitionTime = st.TransitionTime
		log.Printf("Got transition time %d, setting to %d", trans, *st.TransitionTime)
	}

	return lightState

}

func (l Light) setTransition(transTime int) *hue.LightState {
	transTime = transTime / 100 //HUE API uses 1/10th of a second
	utranstime := uint16(transTime)
	newstate := &hue.LightState{
		TransitionTime: &utranstime,
	}
	return newstate
}

func (l Light) setBatchColor(payload *simplejson.Json) {
	var newlightstate *hue.LightState

	color := payload.Get("color")
	if color != nil {
		newlightstate = l.setColor(payload)
	}

	if brightness, err := payload.Get("brightness").Float64(); err == nil {
		bri := l.setBrightness(brightness)
		newlightstate.Brightness = bri.Brightness
	}

	if onoff, err := payload.Get("on-off").Bool(); err == nil {
		st := l.turnOnOff(onoff)
		newlightstate.On = st.On
	}

	if transition, err := payload.Get("transition").Int(); err == nil {
		trans := l.setTransition(transition)
		newlightstate.TransitionTime = trans.TransitionTime
	}

	l.sendLightState(newlightstate)

}

func createLightState() *hue.LightState {

	on := bool(false)
	brightness := uint8(0)
	saturation := uint8(0)
	hueVal := uint16(0)
	transitionTime := uint16(0)
	alert := ""
	temp := uint16(0)
	xy := []float64{0, 0}

	lightState := &hue.LightState{
		On:             &on,
		Brightness:     &brightness,
		Saturation:     &saturation,
		Hue:            &hueVal,
		ColorTemp:      &temp,
		TransitionTime: &transitionTime,
		Alert:          alert,
		XY:             xy,
	}

	return lightState
}

func getCurDir() string {
	pwd, _ := os.Getwd()
	return pwd + "/"
}

func (l Light) sendLightState(newstate *hue.LightState) {
	l.User.SetLightState(l.Id, newstate)
	l.OnOffBus.SendEvent("state", l.GetJsonLightState())
}

func (l Light) refreshLightState() { //TOOD fix verboseness
	newstate, _ := l.User.GetLightAttributes(l.Id)
	bulbstate := newstate.State
	mybulbstate := l.LightState
	transtime := uint16(5) //TODO: Remove when web is sending sensible value
	// l.LightState = newstate.State //TODO why doesnt this work?

	mybulbstate.On = bulbstate.On
	mybulbstate.Brightness = bulbstate.Brightness
	mybulbstate.Hue = bulbstate.Hue
	mybulbstate.Saturation = bulbstate.Saturation
	mybulbstate.XY = bulbstate.XY
	mybulbstate.ColorTemp = bulbstate.ColorTemp
	mybulbstate.Alert = bulbstate.Alert
	mybulbstate.Effect = bulbstate.Effect
	mybulbstate.TransitionTime = &transtime

}

//Returns json state as defined by Ninja light protocol
func (l Light) GetJsonLightState() *simplejson.Json {
	st := l.LightState
	js := simplejson.New()
	js.Set("on", st.On)
	js.Set("bri", st.Brightness)
	js.Set("sat", st.Saturation)
	js.Set("hue", st.Hue)
	js.Set("ct", st.ColorTemp)
	js.Set("transitionTime", st.TransitionTime)
	js.Set("alert", st.Alert)
	js.Set("xy", st.XY)
	return js
}

func printState(s *hue.LightState) {
	spew.Dump(s)
}

func main() {

	conn, err := ninja.Connect("com.ninjablocks.hue")
	if err != nil {
		log.Fatalf("Could not connect to MQTT: %s", err)
	}

	bus, err := conn.AnnounceDriver("com.ninjablocks.hue", "driver-hue", getCurDir())
	if err != nil {
		log.Fatalf("Could not get driver bus: %s", err)
	}

	bridge := getBridge()
	user := getUser(bridge)

	allLights, err := user.GetLights()
	if err != nil {
		log.Fatalf("Couldn't get lights:  %s", err)
	}

	for _, l := range allLights {
		_, err := NewLight(&l, bus, bridge, user)
		if err != nil {
			log.Fatalf("Error creating light instance:  %s", err)
		}

	}

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, os.Kill)

	// Block until a signal is received.
	s := <-c
	fmt.Println("Got signal:", s)

}

// func verifyState (sent *simpleJson) bool { TODO
//if sent state == current state, return true
//else return false
// }
<commit_msg>shhhhh<commit_after>package main

import (
	"fmt"
	"log"
	"math"
	"os"
	"os/signal"
	"strings"
	"time"

	"github.com/bcurren/go-hue"
	"github.com/bitly/go-simplejson"
	"github.com/davecgh/go-spew/spew"
	"github.com/ninjasphere/go-ninja"
)

var _ = fmt.Printf

type Light struct {
	Id            string
	Name          string
	Bus           *ninja.DeviceBus
	OnOffBus      *ninja.ChannelBus
	colorBus      *ninja.ChannelBus
	brightnessBus *ninja.ChannelBus
	Bridge        *hue.Bridge
	User          *hue.User
	LightState    *hue.LightState
	Batch         bool
	batchBus      *ninja.ChannelBus
	//bulbMonitor   *bulbmonitor.BulbMonitor
}

//--------------------------------------------------[HUE BASESTATION]----------------------------------------------------------------------

func getBridge() *hue.Bridge {
	nobridge := true
	var allbridges []*hue.Bridge
	for nobridge {
		allbridges, _ = hue.FindBridges()
		if len(allbridges) == 0 {
			time.Sleep(time.Second * 2) //this sucks
		} else {
			nobridge = false
			log.Printf("got %d bridges: %s", len(allbridges), allbridges)
		}
	}
	return allbridges[0]
}

func getUser(bridge *hue.Bridge) *hue.User {
	var user *hue.User
	var err error
	noUser := true
	retries := 0
	serial, err := ninja.GetSerial()
	if err != nil {
		log.Fatalf("Problem determining if hue user is valid: %s", err)
	}
	username := serial + serial //username must be long 10-40 characters
	isvaliduser, err := bridge.IsValidUser(username)
	if err != nil {
		log.Printf("Problem determining if hue user is valid")
	}

	if isvaliduser {
		user = hue.NewUserWithBridge(username, bridge)
	} else {
		for noUser {
			user, err = bridge.CreateUser("ninjadevice", username)
			if err != nil {
				if strings.Contains(err.Error(), "101") { // there's probably a nicer way to check this
					retries++
					log.Printf("Couldn't make user, push link button. Retry: %d", retries)
					time.Sleep(time.Second * 2) //this sucks
				} else {
					log.Fatalf("Error creating user: %s", err)
				}
			}

			if user != nil {
				noUser = false
			}
		}
	}
	return user
}

//--------------------------------------------------[BUSES]----------------------------------------------------------------------

func getOnOffBus(light *Light) *ninja.ChannelBus {
	methods := []string{"turnOn", "turnOff", "set"}
	events := []string{"state"}
	onOffBus, _ := light.Bus.AnnounceChannel("on-off", "on-off", methods, events, func(method string, payload *simplejson.Json) {
		if light.Batch == true {
			return
		}
		switch method {
		case "turnOn":
			light.sendLightState(light.turnOnOff(true))
		case "turnOff":
			light.sendLightState(light.turnOnOff(false))
		case "set":
			state, _ := payload.GetIndex(0).Bool()
			light.sendLightState(light.turnOnOff(state))
		default:
			log.Printf("On-off got an unknown method %s", method)
			return
		}
	})

	return onOffBus
}

func getBrightBus(light *Light) *ninja.ChannelBus {
	methods := []string{"set"}
	events := []string{"state"}
	brightnessBus, _ := light.Bus.AnnounceChannel("brightness", "brightness", methods, events, func(method string, payload *simplejson.Json) {
		if light.Batch == true {
			return
		}

		switch method {
		case "set":
			brightness, _ := payload.GetIndex(0).Float64()
			log.Printf("Setting brightness to %f!", brightness)
			light.sendLightState(light.setBrightness(brightness))
		default:
			log.Printf("Brightness got an unknown method %s", method)
			return
		}

	})

	return brightnessBus

}

func getColorBus(light *Light) *ninja.ChannelBus {
	methods := []string{"set"}
	events := []string{"state"}
	colorBus, _ := light.Bus.AnnounceChannel("color", "color", methods, events, func(method string, payload *simplejson.Json) {
		if light.Batch == true {
			return
		}
		switch method {
		case "set":
			log.Printf("Setting color to %s!", payload)
			light.sendLightState(light.setColor(payload))
		default:
			log.Printf("Color got an unknown method %s", method)
		}
	})

	return colorBus
}

func getBatchBus(light *Light) *ninja.ChannelBus {
	methods := []string{"setBatch"}
	events := []string{"state"}
	batchBus, _ := light.Bus.AnnounceChannel("core.batching", "core.batching", methods, events, func(method string, payload *simplejson.Json) {
		switch method {
		case "setBatch":
			light.setBatchColor(payload.GetIndex(0))
		default:
			log.Printf("Color got an unknown method %s", method)
			return
		}
	})

	return batchBus
}

func NewLight(l *hue.Light, bus *ninja.DriverBus, bridge *hue.Bridge, user *hue.User) (*Light, error) { //TODO cut this down!

	lightState := createLightState()

	light := &Light{
		Id:         l.Id,
		Name:       l.Name,
		Bridge:     bridge,
		User:       user,
		LightState: lightState,
		Batch:      false,
	}

	sigs, _ := simplejson.NewJson([]byte(`{
			"ninja:manufacturer": "Phillips",
			"ninja:productName": "Hue",
			"manufacturer:productModelId": "",
			"ninja:productType": "Light",
			"ninja:thingType": "light"
	}`))

	la, _ := user.GetLightAttributes(l.Id)
	sigs.Set("manufacturer:productModelId", la.ModelId)

	deviceBus, _ := bus.AnnounceDevice(l.Id, "hue", l.Name, sigs)
	light.Bus = deviceBus
	light.OnOffBus = getOnOffBus(light)
	light.brightnessBus = getBrightBus(light)
	light.colorBus = getColorBus(light)
	light.batchBus = getBatchBus(light)

	return light, nil
}

func (l Light) turnOnOff(state bool) *hue.LightState {
	lightState := &hue.LightState{
		On: &state,
	}
	return lightState

}

func (l Light) setBrightness(fbrightness float64) *hue.LightState {
	brightness := uint8(fbrightness * math.MaxUint8)
	lightState := &hue.LightState{
		Brightness: &brightness,
	}
	return lightState
}

func (l Light) setColor(payload *simplejson.Json) *hue.LightState {

	var lightState *hue.LightState
	colorpayload := payload.Get("color")
	mode, err := colorpayload.Get("mode").String()
	if err != nil {
		log.Printf("No mode sent to color bus: %s", err)
		spew.Dump(payload)
	}

	switch mode {
	case "hue": //TODO less verbose plz
		fhue, err := colorpayload.Get("hue").Float64()
		if err != nil {
			log.Printf("No hue sent to color bus: %s", err)
			spew.Dump(payload)
		}
		uint16hue := uint16(fhue * math.MaxUint16)
		fsaturation, err := colorpayload.Get("saturation").Float64()
		if err != nil {
			log.Printf("No saturation sent to color bus: %s", err)
			spew.Dump(payload)
		}
		saturation := uint8(fsaturation * math.MaxUint8)
		lightState = &hue.LightState{
			Hue:        &uint16hue,
			Saturation: &saturation,
		}

	case "xy":
		x, err := colorpayload.Get("x").Float64()
		if err != nil {
			log.Printf("X coord not sent to color bus: %s", err)
			spew.Dump(payload)
		}
		y, err := colorpayload.Get("y").Float64()
		if err != nil {
			log.Printf("Y coord not sent to color bus: %s", err)
			spew.Dump(payload)
		}
		xy := []float64{x, y}
		lightState = &hue.LightState{
			XY: xy,
		}
	case "temperature":
		temp, err := colorpayload.Get("temperature").Float64()
		if err != nil {
			log.Printf("No temperature sent to color bus: %s", err)
			spew.Dump(payload)
		}
		utemp := uint16(math.Floor(1000000 / temp))
		lightState = &hue.LightState{
			ColorTemp: &utemp,
		}

	default:
		log.Printf("Bad color mode: %s", mode)
		return nil
	}

	if trans, e := payload.Get("transition").Int(); e == nil {
		st := l.setTransition(trans)
		lightState.TransitionTime = st.TransitionTime
	}

	return lightState

}

func (l Light) setTransition(transTime int) *hue.LightState {
	transTime = transTime / 100 //HUE API uses 1/10th of a second
	utranstime := uint16(transTime)
	newstate := &hue.LightState{
		TransitionTime: &utranstime,
	}
	return newstate
}

func (l Light) setBatchColor(payload *simplejson.Json) {
	var newlightstate *hue.LightState

	color := payload.Get("color")
	if color != nil {
		newlightstate = l.setColor(payload)
	}

	if brightness, err := payload.Get("brightness").Float64(); err == nil {
		bri := l.setBrightness(brightness)
		newlightstate.Brightness = bri.Brightness
	}

	if onoff, err := payload.Get("on-off").Bool(); err == nil {
		st := l.turnOnOff(onoff)
		newlightstate.On = st.On
	}

	if transition, err := payload.Get("transition").Int(); err == nil {
		trans := l.setTransition(transition)
		newlightstate.TransitionTime = trans.TransitionTime
	}

	l.sendLightState(newlightstate)

}

func createLightState() *hue.LightState {

	on := bool(false)
	brightness := uint8(0)
	saturation := uint8(0)
	hueVal := uint16(0)
	transitionTime := uint16(0)
	alert := ""
	temp := uint16(0)
	xy := []float64{0, 0}

	lightState := &hue.LightState{
		On:             &on,
		Brightness:     &brightness,
		Saturation:     &saturation,
		Hue:            &hueVal,
		ColorTemp:      &temp,
		TransitionTime: &transitionTime,
		Alert:          alert,
		XY:             xy,
	}

	return lightState
}

func getCurDir() string {
	pwd, _ := os.Getwd()
	return pwd + "/"
}

func (l Light) sendLightState(newstate *hue.LightState) {
	l.User.SetLightState(l.Id, newstate)
	l.OnOffBus.SendEvent("state", l.GetJsonLightState())
}

func (l Light) refreshLightState() { //TOOD fix verboseness
	newstate, _ := l.User.GetLightAttributes(l.Id)
	bulbstate := newstate.State
	mybulbstate := l.LightState
	transtime := uint16(5) //TODO: Remove when web is sending sensible value
	// l.LightState = newstate.State //TODO why doesnt this work?

	mybulbstate.On = bulbstate.On
	mybulbstate.Brightness = bulbstate.Brightness
	mybulbstate.Hue = bulbstate.Hue
	mybulbstate.Saturation = bulbstate.Saturation
	mybulbstate.XY = bulbstate.XY
	mybulbstate.ColorTemp = bulbstate.ColorTemp
	mybulbstate.Alert = bulbstate.Alert
	mybulbstate.Effect = bulbstate.Effect
	mybulbstate.TransitionTime = &transtime

}

//Returns json state as defined by Ninja light protocol
func (l Light) GetJsonLightState() *simplejson.Json {
	st := l.LightState
	js := simplejson.New()
	js.Set("on", st.On)
	js.Set("bri", st.Brightness)
	js.Set("sat", st.Saturation)
	js.Set("hue", st.Hue)
	js.Set("ct", st.ColorTemp)
	js.Set("transitionTime", st.TransitionTime)
	js.Set("alert", st.Alert)
	js.Set("xy", st.XY)
	return js
}

func printState(s *hue.LightState) {
	spew.Dump(s)
}

func main() {

	conn, err := ninja.Connect("com.ninjablocks.hue")
	if err != nil {
		log.Fatalf("Could not connect to MQTT: %s", err)
	}

	bus, err := conn.AnnounceDriver("com.ninjablocks.hue", "driver-hue", getCurDir())
	if err != nil {
		log.Fatalf("Could not get driver bus: %s", err)
	}

	bridge := getBridge()
	user := getUser(bridge)

	allLights, err := user.GetLights()
	if err != nil {
		log.Fatalf("Couldn't get lights:  %s", err)
	}

	for _, l := range allLights {
		_, err := NewLight(&l, bus, bridge, user)
		if err != nil {
			log.Fatalf("Error creating light instance:  %s", err)
		}

	}

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, os.Kill)

	// Block until a signal is received.
	s := <-c
	fmt.Println("Got signal:", s)

}

// func verifyState (sent *simpleJson) bool { TODO
//if sent state == current state, return true
//else return false
// }
<|endoftext|>