Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adding activity type to heartbeat #3

Merged
merged 10 commits into from
Mar 20, 2019
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
example-start:
go run example/tracker.go
go run example/example.go
13 changes: 9 additions & 4 deletions example/tracker.go → example/example.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import (
"fmt"
"time"

"github.com/prashantgupta24/activity-tracker/src/activity"
"github.com/prashantgupta24/activity-tracker/pkg/tracker"
)

func main() {
fmt.Println("starting activity tracker")

timeToCheck := 5

activityTracker := &activity.ActivityTracker{
activityTracker := &tracker.Instance{
TimeToCheck: time.Duration(timeToCheck),
}
heartbeatCh, quitActivityTracker := activityTracker.Start()
Expand All @@ -23,9 +23,14 @@ func main() {
select {
case heartbeat := <-heartbeatCh:
if !heartbeat.IsActivity {
fmt.Printf("no activity detected in the last %v seconds\n", int(timeToCheck))
fmt.Printf("no activity detected in the last %v seconds\n\n", int(timeToCheck))
} else {
fmt.Printf("activity detected in the last %v seconds\n", int(timeToCheck))
fmt.Printf("activity detected in the last %v seconds. ", int(timeToCheck))
fmt.Printf("Activity type:\n")
for activity, time := range heartbeat.Activity {
fmt.Printf("%v at %v\n", activity.ActivityType, time)
}
fmt.Println()
}
case <-timeToKill.C:
fmt.Println("time to kill app")
Expand Down
4 changes: 3 additions & 1 deletion src/mouse/mouse.go → internal/pkg/mouse/mouse.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package mouse

import "github.com/go-vgo/robotgo"
import (
"github.com/go-vgo/robotgo"
)

type Position struct {
MouseX int
Expand Down
64 changes: 64 additions & 0 deletions internal/pkg/service/mouseClickHandler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package service

import (
"log"
"time"

"github.com/go-vgo/robotgo"
"github.com/prashantgupta24/activity-tracker/pkg/activity"
)

type MouseClickHandler struct {
tickerCh chan struct{}
}

func (m *MouseClickHandler) Start(activityCh chan *activity.Type) {
m.tickerCh = make(chan struct{})
registrationFree := make(chan struct{})

go func() {
go addMouseClickRegistration(activityCh, registrationFree) //run once before first check
for range m.tickerCh {
log.Printf("mouse clicker checked at : %v\n", time.Now())
select {
case _, ok := <-registrationFree:
if ok {
//log.Printf("registration free for mouse click \n")
go addMouseClickRegistration(activityCh, registrationFree)
} else {
//log.Printf("error : channel closed \n")
return
}
default:
//log.Printf("registration is busy for mouse click handler, do nothing\n")
}
}
log.Printf("stopping click handler")
return
}()
}

func (m *MouseClickHandler) Trigger() {
//doing it the non-blocking sender way
select {
case m.tickerCh <- struct{}{}:
default:
//service is blocked, handle it somehow?
}
}
func (m *MouseClickHandler) Close() {
close(m.tickerCh)
}

func addMouseClickRegistration(activityCh chan *activity.Type, registrationFree chan struct{}) {
log.Printf("adding reg \n")
mleft := robotgo.AddEvent("mleft")
if mleft {
//log.Printf("mleft clicked \n")
activityCh <- &activity.Type{
ActivityType: activity.MOUSE_LEFT_CLICK,
}
registrationFree <- struct{}{}
return
}
}
76 changes: 76 additions & 0 deletions internal/pkg/service/mouseCursorHandler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package service

import (
"log"
"time"

"github.com/prashantgupta24/activity-tracker/internal/pkg/mouse"
"github.com/prashantgupta24/activity-tracker/pkg/activity"
)

type MouseCursorHandler struct {
tickerCh chan struct{}
}

type cursorInfo struct {
didCursorMove bool
currentMousePos *mouse.Position
}

func (m *MouseCursorHandler) Start(activityCh chan *activity.Type) {

m.tickerCh = make(chan struct{})

go func() {
lastMousePos := mouse.GetPosition()
for range m.tickerCh {
log.Printf("mouse cursor checked at : %v\n", time.Now())
commCh := make(chan *cursorInfo)
go checkCursorChange(commCh, lastMousePos)
select {
case cursorInfo := <-commCh:
if cursorInfo.didCursorMove {
activityCh <- &activity.Type{
ActivityType: activity.MOUSE_CURSOR_MOVEMENT,
}
lastMousePos = cursorInfo.currentMousePos
}
case <-time.After(timeout * time.Millisecond):
//timeout, do nothing
log.Printf("timeout happened after %vms while checking mouse cursor handler", timeout)
}
}
log.Printf("stopping cursor handler")
return
}()
}

func (m *MouseCursorHandler) Trigger() {
//doing it the non-blocking sender way
select {
case m.tickerCh <- struct{}{}:
default:
//service is blocked, handle it somehow?
}
}
func (m *MouseCursorHandler) Close() {
close(m.tickerCh)
}

func checkCursorChange(commCh chan *cursorInfo, lastMousePos *mouse.Position) {
currentMousePos := mouse.GetPosition()
//log.Printf("current mouse position: %v\n", currentMousePos)
//log.Printf("last mouse position: %v\n", lastMousePos)
if currentMousePos.MouseX == lastMousePos.MouseX &&
currentMousePos.MouseY == lastMousePos.MouseY {
commCh <- &cursorInfo{
didCursorMove: false,
currentMousePos: nil,
}
} else {
commCh <- &cursorInfo{
didCursorMove: true,
currentMousePos: currentMousePos,
}
}
}
79 changes: 79 additions & 0 deletions internal/pkg/service/screenChangeHandler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package service

import (
"log"
"time"

"github.com/go-vgo/robotgo"
"github.com/prashantgupta24/activity-tracker/pkg/activity"
)

type ScreenChangeHandler struct {
tickerCh chan struct{}
}

type screenInfo struct {
didScreenChange bool
currentPixelColor string
}

func (s *ScreenChangeHandler) Start(activityCh chan *activity.Type) {

s.tickerCh = make(chan struct{})

go func() {
screenSizeX, screenSizeY := robotgo.GetScreenSize()
pixelPointX := int(screenSizeX / 2)
pixelPointY := int(screenSizeY / 2)
lastPixelColor := robotgo.GetPixelColor(pixelPointX, pixelPointY)
for range s.tickerCh {
log.Printf("screen change checked at : %v\n", time.Now())
commCh := make(chan *screenInfo)
go checkScreenChange(commCh, lastPixelColor, pixelPointX, pixelPointY)
select {
case screenInfo := <-commCh:
if screenInfo.didScreenChange {
activityCh <- &activity.Type{
ActivityType: activity.SCREEN_CHANGE,
}
lastPixelColor = screenInfo.currentPixelColor
}
case <-time.After(timeout * time.Millisecond):
//timeout, do nothing
log.Printf("timeout happened after %vms while checking screen change handler", timeout)
}
}
log.Printf("stopping screen change handler")
return
}()
}

func (s *ScreenChangeHandler) Trigger() {
//doing it the non-blocking sender way
select {
case s.tickerCh <- struct{}{}:
default:
//service is blocked, handle it somehow?
}
}
func (s *ScreenChangeHandler) Close() {
close(s.tickerCh)
}

func checkScreenChange(commCh chan *screenInfo, lastPixelColor string, pixelPointX, pixelPointY int) {
currentPixelColor := robotgo.GetPixelColor(pixelPointX, pixelPointY)
// log.Printf("current pixel color: %v\n", currentPixelColor)
// log.Printf("last pixel color: %v\n", lastPixelColor)
//robotgo.MoveMouse(pixelPointX, pixelPointY)
if lastPixelColor != currentPixelColor {
commCh <- &screenInfo{
didScreenChange: true,
currentPixelColor: currentPixelColor,
}
} else { //comment this section out to test timeout logic
commCh <- &screenInfo{
didScreenChange: false,
currentPixelColor: "",
}
}
}
13 changes: 13 additions & 0 deletions internal/pkg/service/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package service

import "github.com/prashantgupta24/activity-tracker/pkg/activity"

const (
timeout = 100 //ms
)

type Instance interface {
Start(chan *activity.Type)
Trigger()
Close()
}
13 changes: 13 additions & 0 deletions pkg/activity/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package activity

type activityType string

const (
MOUSE_CURSOR_MOVEMENT activityType = "cursor-move"
MOUSE_LEFT_CLICK activityType = "left-mouse-click"
SCREEN_CHANGE activityType = "screen-change"
)

type Type struct {
ActivityType activityType
}
96 changes: 96 additions & 0 deletions pkg/tracker/tracker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package tracker

import (
"log"
"time"

"github.com/prashantgupta24/activity-tracker/internal/pkg/service"
"github.com/prashantgupta24/activity-tracker/pkg/activity"
)

const (
preHeartbeatTime = time.Millisecond * 100
)

func (tracker *Instance) Start() (heartbeatCh chan *Heartbeat, quit chan struct{}) {

//register service handlers
tracker.registerHandlers(&service.MouseClickHandler{}, &service.MouseCursorHandler{},
&service.ScreenChangeHandler{})

//returned channels
heartbeatCh = make(chan *Heartbeat, 1)
quit = make(chan struct{})

go func(tracker *Instance) {
timeToCheck := tracker.TimeToCheck
//tickers
tickerHeartbeat := time.NewTicker(time.Second * timeToCheck)
tickerWorker := time.NewTicker(time.Second*timeToCheck - preHeartbeatTime)

activities := makeActivityMap()

for {
select {
case <-tickerWorker.C:
log.Printf("tracker worker working at %v\n", time.Now())
//time to trigger all registered services
for service := range tracker.services {
service.Trigger()
}
case <-tickerHeartbeat.C:
log.Printf("tracker heartbeat checking at %v\n", time.Now())
var heartbeat *Heartbeat
if len(activities) == 0 {
//log.Printf("no activity detected in the last %v seconds ...\n", int(timeToCheck))
heartbeat = &Heartbeat{
IsActivity: false,
Activity: nil,
Time: time.Now(),
}
} else {
//log.Printf("activity detected in the last %v seconds ...\n", int(timeToCheck))
heartbeat = &Heartbeat{
IsActivity: true,
Activity: activities,
Time: time.Now(),
}

}
heartbeatCh <- heartbeat
activities = makeActivityMap() //reset the activities map
case activity := <-tracker.activityCh:
activities[activity] = time.Now()
//log.Printf("activity received: %#v\n", activity)
case <-quit:
log.Printf("stopping activity tracker\n")
//close all services for a clean exit
for service := range tracker.services {
service.Close()
}
return
}
}
}(tracker)

return heartbeatCh, quit
}

func makeActivityMap() map[*activity.Type]time.Time {
activityMap := make(map[*activity.Type]time.Time)
return activityMap
}

func (tracker *Instance) registerHandlers(services ...service.Instance) {

tracker.services = make(map[service.Instance]bool)
tracker.activityCh = make(chan *activity.Type, len(services)) // number based on types of activities being tracked

for _, service := range services {
service.Start(tracker.activityCh)
if _, ok := tracker.services[service]; !ok { //duplicate registration prevention
tracker.services[service] = true
}

}
}
Loading