Skip to content

Commit

Permalink
Merge pull request #3 from prashantgupta24/dev1
Browse files Browse the repository at this point in the history
adding activity type to heartbeat
  • Loading branch information
prashantgupta24 authored Mar 20, 2019
2 parents d567f73 + e96d248 commit 0198d73
Show file tree
Hide file tree
Showing 12 changed files with 374 additions and 134 deletions.
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

0 comments on commit 0198d73

Please sign in to comment.