mirror of https://github.com/matrix-org/go-neb.git
Browse Source
Merge pull request #76 from matrix-org/kegan/rss
Merge pull request #76 from matrix-org/kegan/rss
Implement polling for Servicespull/68/merge
Kegsay
8 years ago
committed by
GitHub
12 changed files with 330 additions and 29 deletions
-
12src/github.com/matrix-org/go-neb/api.go
-
13src/github.com/matrix-org/go-neb/database/db.go
-
27src/github.com/matrix-org/go-neb/database/schema.go
-
5src/github.com/matrix-org/go-neb/goneb.go
-
103src/github.com/matrix-org/go-neb/polling/polling.go
-
13src/github.com/matrix-org/go-neb/services/echo/echo.go
-
5src/github.com/matrix-org/go-neb/services/giphy/giphy.go
-
7src/github.com/matrix-org/go-neb/services/github/github.go
-
7src/github.com/matrix-org/go-neb/services/github/github_webhook.go
-
8src/github.com/matrix-org/go-neb/services/jira/jira.go
-
111src/github.com/matrix-org/go-neb/services/rss/rss.go
-
48src/github.com/matrix-org/go-neb/types/types.go
@ -0,0 +1,103 @@ |
|||||
|
package polling |
||||
|
|
||||
|
import ( |
||||
|
"fmt" |
||||
|
log "github.com/Sirupsen/logrus" |
||||
|
"github.com/matrix-org/go-neb/database" |
||||
|
"github.com/matrix-org/go-neb/types" |
||||
|
"sync" |
||||
|
"time" |
||||
|
) |
||||
|
|
||||
|
// Remember when we first started polling on this service ID. Polling routines will
|
||||
|
// continually check this time. If the service gets updated, this will change, prompting
|
||||
|
// older instances to die away. If this service gets removed, the time will be 0.
|
||||
|
var ( |
||||
|
pollMutex sync.Mutex |
||||
|
startPollTime = make(map[string]int64) // ServiceID => unix timestamp
|
||||
|
) |
||||
|
|
||||
|
// Start polling already existing services
|
||||
|
func Start() error { |
||||
|
// Work out which service types require polling
|
||||
|
for serviceType, poller := range types.PollersByType() { |
||||
|
if poller == nil { |
||||
|
continue |
||||
|
} |
||||
|
// Query for all services with said service type
|
||||
|
srvs, err := database.GetServiceDB().LoadServicesByType(serviceType) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
for _, s := range srvs { |
||||
|
if err := StartPolling(s); err != nil { |
||||
|
return err |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
// StartPolling begins a polling loop for this service.
|
||||
|
// If one already exists for this service, it will be instructed to die. The new poll will not wait for this to happen,
|
||||
|
// so there may be a brief period of overlap. It is safe to immediately call `StopPolling(service)` to immediately terminate
|
||||
|
// this poll.
|
||||
|
func StartPolling(service types.Service) error { |
||||
|
p := types.PollersByType()[service.ServiceType()] |
||||
|
if p == nil { |
||||
|
return fmt.Errorf("Service %s (type=%s) doesn't have a Poller", service.ServiceID(), service.ServiceType()) |
||||
|
} |
||||
|
// Set the poll time BEFORE spinning off the goroutine in case the caller immediately stops us. If we don't do this here,
|
||||
|
// we risk them setting the ts to 0 BEFORE we've set the start time, resulting in a poll when one was not intended.
|
||||
|
ts := time.Now().UnixNano() |
||||
|
setPollStartTime(service, ts) |
||||
|
go pollLoop(service, p, ts) |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
// StopPolling stops all pollers for this service.
|
||||
|
func StopPolling(service types.Service) { |
||||
|
log.WithFields(log.Fields{ |
||||
|
"service_id": service.ServiceID(), |
||||
|
"service_type": service.ServiceType(), |
||||
|
}).Info("StopPolling") |
||||
|
setPollStartTime(service, 0) |
||||
|
} |
||||
|
|
||||
|
// pollLoop begins the polling loop for this service. Does not return, so call this
|
||||
|
// as a goroutine!
|
||||
|
func pollLoop(service types.Service, poller types.Poller, ts int64) { |
||||
|
logger := log.WithFields(log.Fields{ |
||||
|
"timestamp": ts, |
||||
|
"service_id": service.ServiceID(), |
||||
|
"service_type": service.ServiceType(), |
||||
|
"interval_secs": poller.IntervalSecs(), |
||||
|
}) |
||||
|
logger.Info("Starting polling loop") |
||||
|
for { |
||||
|
poller.OnPoll(service) |
||||
|
if pollTimeChanged(service, ts) { |
||||
|
logger.Info("Terminating poll.") |
||||
|
break |
||||
|
} |
||||
|
time.Sleep(time.Duration(poller.IntervalSecs()) * time.Second) |
||||
|
if pollTimeChanged(service, ts) { |
||||
|
logger.Info("Terminating poll.") |
||||
|
break |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// setPollStartTime clobbers the current poll time
|
||||
|
func setPollStartTime(service types.Service, startTs int64) { |
||||
|
pollMutex.Lock() |
||||
|
defer pollMutex.Unlock() |
||||
|
startPollTime[service.ServiceID()] = startTs |
||||
|
} |
||||
|
|
||||
|
// pollTimeChanged returns true if the poll start time for this service ID is different to the one supplied.
|
||||
|
func pollTimeChanged(service types.Service, ts int64) bool { |
||||
|
pollMutex.Lock() |
||||
|
defer pollMutex.Unlock() |
||||
|
return startPollTime[service.ServiceID()] != ts |
||||
|
} |
@ -0,0 +1,111 @@ |
|||||
|
package services |
||||
|
|
||||
|
import ( |
||||
|
"errors" |
||||
|
log "github.com/Sirupsen/logrus" |
||||
|
"github.com/matrix-org/go-neb/database" |
||||
|
"github.com/matrix-org/go-neb/matrix" |
||||
|
"github.com/matrix-org/go-neb/polling" |
||||
|
"github.com/matrix-org/go-neb/types" |
||||
|
"time" |
||||
|
) |
||||
|
|
||||
|
type rssPoller struct{} |
||||
|
|
||||
|
func (p *rssPoller) IntervalSecs() int64 { return 10 } |
||||
|
func (p *rssPoller) OnPoll(s types.Service) { |
||||
|
rsss, ok := s.(*rssService) |
||||
|
if !ok { |
||||
|
log.WithField("service_id", s.ServiceID()).Error("RSS: OnPoll called without an RSS Service") |
||||
|
return |
||||
|
} |
||||
|
now := time.Now().Unix() // Second resolution
|
||||
|
// URL => [ RoomID ]
|
||||
|
urlsToRooms := make(map[string][]string) |
||||
|
|
||||
|
for roomID, roomInfo := range rsss.Rooms { |
||||
|
for u, feedInfo := range roomInfo.Feeds { |
||||
|
if feedInfo.LastPollTimestampSecs == 0 || (feedInfo.LastPollTimestampSecs+(int64(feedInfo.PollIntervalMins)*60)) > now { |
||||
|
// re-query this feed
|
||||
|
urlsToRooms[u] = append(urlsToRooms[u], roomID) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// TODO: Some polling
|
||||
|
} |
||||
|
|
||||
|
type rssService struct { |
||||
|
types.DefaultService |
||||
|
id string |
||||
|
serviceUserID string |
||||
|
Rooms map[string]struct { // room_id => {}
|
||||
|
Feeds map[string]struct { // URL => { }
|
||||
|
PollIntervalMins int `json:"poll_interval_mins"` |
||||
|
LastPollTimestampSecs int64 |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func (s *rssService) ServiceUserID() string { return s.serviceUserID } |
||||
|
func (s *rssService) ServiceID() string { return s.id } |
||||
|
func (s *rssService) ServiceType() string { return "rss" } |
||||
|
func (s *rssService) Poller() types.Poller { return &rssPoller{} } |
||||
|
|
||||
|
// Register will check the liveness of each RSS feed given. If all feeds check out okay, no error is returned.
|
||||
|
func (s *rssService) Register(oldService types.Service, client *matrix.Client) error { |
||||
|
feeds := feedUrls(s) |
||||
|
if len(feeds) == 0 { |
||||
|
// this is an error UNLESS the old service had some feeds in which case they are deleting us :(
|
||||
|
oldFeeds := feedUrls(oldService) |
||||
|
if len(oldFeeds) == 0 { |
||||
|
return errors.New("An RSS feed must be specified.") |
||||
|
} |
||||
|
} |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
func (s *rssService) PostRegister(oldService types.Service) { |
||||
|
if len(feedUrls(s)) == 0 { // bye-bye :(
|
||||
|
logger := log.WithFields(log.Fields{ |
||||
|
"service_id": s.ServiceID(), |
||||
|
"service_type": s.ServiceType(), |
||||
|
}) |
||||
|
logger.Info("Deleting service (0 feeds)") |
||||
|
polling.StopPolling(s) |
||||
|
if err := database.GetServiceDB().DeleteService(s.ServiceID()); err != nil { |
||||
|
logger.WithError(err).Error("Failed to delete service") |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// feedUrls returns a list of feed urls for this service
|
||||
|
func feedUrls(srv types.Service) []string { |
||||
|
var feeds []string |
||||
|
s, ok := srv.(*rssService) |
||||
|
if !ok { |
||||
|
return feeds |
||||
|
} |
||||
|
|
||||
|
urlSet := make(map[string]bool) |
||||
|
for _, roomInfo := range s.Rooms { |
||||
|
for u := range roomInfo.Feeds { |
||||
|
urlSet[u] = true |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for u := range urlSet { |
||||
|
feeds = append(feeds, u) |
||||
|
} |
||||
|
return feeds |
||||
|
} |
||||
|
|
||||
|
func init() { |
||||
|
types.RegisterService(func(serviceID, serviceUserID, webhookEndpointURL string) types.Service { |
||||
|
r := &rssService{ |
||||
|
id: serviceID, |
||||
|
serviceUserID: serviceUserID, |
||||
|
} |
||||
|
return r |
||||
|
}) |
||||
|
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue