Skip to content

Commit

Permalink
Service updates (#774)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidnewhall authored Jul 19, 2024
2 parents 36bd026 + 723a707 commit 8b11430
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 26 deletions.
1 change: 1 addition & 0 deletions pkg/client/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ func (c *Client) httpAPIHandlers() {
c.Config.HandleAPIpath("", "version/{app}/{instance:[0-9]+}", c.clientinfo.VersionHandlerInstance, "GET", "HEAD")
c.Config.HandleAPIpath("", "trigger/{trigger:[0-9a-z-]+}", c.triggers.APIHandler, "GET", "POST")
c.Config.HandleAPIpath("", "trigger/{trigger:[0-9a-z-]+}/{content}", c.triggers.APIHandler, "GET", "POST")
c.Config.HandleAPIpath("", "services/{action}", c.Config.Services.APIHandler, "GET")
c.Config.HandleAPIpath("", "triggers", c.triggers.HandleGetTriggers, "GET")
c.Config.HandleAPIpath("", "ping", c.handleInstancePing, "GET")
c.Config.HandleAPIpath("", "ping/{app:[a-z,]+}", c.handleInstancePing, "GET")
Expand Down
4 changes: 4 additions & 0 deletions pkg/services/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,7 @@ func (o *Output) String() string {
func (o *Output) MarshalJSON() ([]byte, error) {
return json.Marshal(o.str) //nolint:wrapcheck // do not unescape it.
}

func (o *Output) UnmarshalJSON(input []byte) error {
return json.Unmarshal(input, &o.str) //nolint:wrapcheck
}
36 changes: 36 additions & 0 deletions pkg/services/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ package services
import (
"context"
"encoding/json"
"net/http"
"strings"
"time"

"github.com/Notifiarr/notifiarr/pkg/logs"
"github.com/Notifiarr/notifiarr/pkg/mnd"
"github.com/Notifiarr/notifiarr/pkg/website"
"github.com/gorilla/mux"
)

func (c *Config) Setup(services []*Service) error {
Expand Down Expand Up @@ -56,6 +58,11 @@ func (c *Config) setup(services []*Service) error {
// Start begins the service check routines.
// Runs Parallel checkers and the check reporter.
func (c *Config) Start(ctx context.Context) {
if len(c.services) == 0 {
c.Printf("==> Service Checker Disabled! No services to check.")
return
}

c.stopLock.Lock()
defer c.stopLock.Unlock()

Expand Down Expand Up @@ -260,3 +267,32 @@ func (c *Config) Stop() {
func (c *Config) SvcCount() int {
return len(c.services)
}

// APIHandler is passed into the webserver so services can be accessed by the API.
func (c *Config) APIHandler(req *http.Request) (int, any) {
return c.handleTrigger(req, website.EventAPI)
}

func (c *Config) handleTrigger(req *http.Request, event website.EventType) (int, any) {
action := mux.Vars(req)["action"]
c.Debugf("[%s requested] Incoming Service Action: %s (%s)", event, action)

switch action {
case "list":
return c.returnServiceList()
default:
return http.StatusBadRequest, "unknown service action: " + action
}
}

// @Description Returns a list of service check results.
// @Summary Get service check results
// @Tags Triggers
// @Produce json
// @Success 200 {object} apps.Respond.apiResponse{message=[]CheckResult} "list check results"
// @Failure 404 {object} string "bad token or api key"
// @Router /api/services/list [get]
// @Security ApiKeyAuth
func (c *Config) returnServiceList() (int, any) {
return http.StatusOK, c.GetResults()
}
9 changes: 9 additions & 0 deletions pkg/triggers/common/triggers.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ type Config struct {
rand *rand.Rand
}

type Create interface {
Create()
}

type Run interface {
Run(ctx context.Context)
Stop()
}

// SetReloadCh is used to set the reload channel for triggers.
// This is an exported method because the channel is not always
// available when triggers are initialized.
Expand Down
59 changes: 50 additions & 9 deletions pkg/triggers/crontimer/custom.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,21 @@ import (
)

// TrigPollSite is our site polling trigger identifier.
const TrigPollSite common.TriggerName = "Polling Notifiarr for new settings."
const (
TrigPollSite common.TriggerName = "Polling Notifiarr for new settings."
TrigUpCheck common.TriggerName = "Telling Notifiarr website we are still up!"
)

const (
// How often to poll the website for changes.
// This only fires when:
// 1. the cliet isn't reachable from the website.
// 1. the client isn't reachable from the website.
// 2. the client didn't get a valid response to clientInfo.
pollDur = 4 * time.Minute
pollDur = 4 * time.Minute
// This just tells the website the client is up.
upCheckDur = 14*time.Minute + 57*time.Second
// How long to be up before sending first up check.
checkWait = 1*time.Minute + 23*time.Second
randomMilliseconds = 5000
randomSeconds = 30
)
Expand Down Expand Up @@ -79,6 +86,21 @@ func (a *Action) Create() {
a.cmd.create()
}

// Stop satisifies an interface.
func (a *Action) Stop() {}

// Verify the interfaces are satisfied.
var (
_ = common.Run(&Action{nil})
_ = common.Create(&Action{nil})
)

// Run fires in a go routine. Wait a minute or two then tell the website we're up.
func (a *Action) Run(ctx context.Context) {
time.Sleep(checkWait)
a.cmd.PollUpCheck(ctx, &common.ActionInput{Type: website.EventStart})
}

func (c *cmd) create() {
ci := clientinfo.Get()
// This poller is sorta shoehorned in here for lack of a better place to put it.
Expand All @@ -87,6 +109,13 @@ func (c *cmd) create() {
return
}

c.Printf("==> Started Notifiarr Website Up-Checker, interval: %s", durafmt.Parse(upCheckDur))
c.Add(&common.Action{
Name: TrigUpCheck,
Fn: c.PollUpCheck,
D: cnfg.Duration{Duration: upCheckDur},
})

for _, custom := range ci.Actions.Custom {
timer := &Timer{
CronConfig: custom,
Expand All @@ -95,13 +124,11 @@ func (c *cmd) create() {
}
custom.URI = "/" + strings.TrimPrefix(custom.URI, "/")

var randomTime time.Duration

if custom.Interval.Duration < time.Minute {
c.Errorf("Website provided custom cron interval under 1 minute. Ignored! Interval: %s Name: %s, URI: %s",
c.Errorf("Website provided custom cron interval under 1 minute. Interval: %s Name: %s, URI: %s",
custom.Interval, custom.Name, custom.URI)
} else {
randomTime = time.Duration(c.Config.Rand().Intn(randomMilliseconds)) * time.Millisecond

custom.Interval.Duration = time.Minute
}

c.list = append(c.list, timer)
Expand All @@ -110,7 +137,7 @@ func (c *cmd) create() {
Name: common.TriggerName(fmt.Sprintf("Running Custom Cron Timer '%s'", custom.Name)),
Fn: timer.run,
C: timer.ch,
D: cnfg.Duration{Duration: custom.Interval.Duration + randomTime},
D: cnfg.Duration{Duration: custom.Interval.Duration},
})
}

Expand All @@ -130,6 +157,20 @@ func (c *cmd) startWebsitePoller() {
})
}

func (c *cmd) PollUpCheck(ctx context.Context, input *common.ActionInput) {
_, err := c.GetData(&website.Request{
Route: website.ClientRoute,
Event: website.EventCheck,
Payload: c.CIC.Info(ctx, true), // true avoids polling tautulli.
LogPayload: false,
ErrorsOnly: true,
})
if err != nil {
c.Errorf("[%s requested] Polling Notifiarr: %v", input.Type, err)
return
}
}

// PollForReload is only started if the initial connection to the website failed.
// This will keep checking until it works, then reload to grab settings and start properly.
func (c *cmd) PollForReload(ctx context.Context, input *common.ActionInput) {
Expand Down
14 changes: 10 additions & 4 deletions pkg/triggers/filewatch/filewatch.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package filewatch

import (
"context"
"fmt"
"io"
"path/filepath"
Expand Down Expand Up @@ -118,8 +119,11 @@ func checkIgnored(ignored []string) ignored {
return output
}

// Verify the interfaces are satisfied.
var _ = common.Run(&Action{nil})

// Run compiles any regexp's and opens a tail -f on provided watch files.
func (a *Action) Run() {
func (a *Action) Run(_ context.Context) {
a.cmd.run()
}

Expand All @@ -146,10 +150,12 @@ func (c *cmd) run() {
validTails = append(validTails, item)
}

if len(validTails) != 0 {
cases, ticker := c.collectFileTails(validTails)
go c.tailFiles(cases, validTails, ticker)
if len(validTails) == 0 {
return
}

cases, ticker := c.collectFileTails(validTails)
c.tailFiles(cases, validTails, ticker)
}

func (w *WatchFile) setup(logger *logger, ignored ignored) error {
Expand Down
17 changes: 4 additions & 13 deletions pkg/triggers/triggers.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,15 +94,6 @@ func New(config *Config) *Actions {
// These methods use reflection so they never really need to be updated.
// They execute all Create(), Run() and Stop() procedures defined in our Actions.

type create interface {
Create()
}

type run interface {
Run()
Stop()
}

// Start creates all the triggers and runs the timers.
func (a *Actions) Start(ctx context.Context, reloadCh chan os.Signal) {
a.Timers.SetReloadCh(reloadCh)
Expand All @@ -115,12 +106,12 @@ func (a *Actions) Start(ctx context.Context, reloadCh chan os.Signal) {
}

// A panic here means you screwed up the code somewhere else.
if action, ok := actions.Field(i).Interface().(create); ok {
if action, ok := actions.Field(i).Interface().(common.Create); ok {
action.Create()
}
// No 'else if' so you can have both if you need them.
if action, ok := actions.Field(i).Interface().(run); ok {
action.Run()
if action, ok := actions.Field(i).Interface().(common.Run); ok {
go action.Run(ctx)
}
}
}
Expand All @@ -136,7 +127,7 @@ func (a *Actions) Stop(event website.EventType) {
continue
}

if action, ok := actions.Field(i).Interface().(run); ok {
if action, ok := actions.Field(i).Interface().(common.Run); ok {
action.Stop()
}
}
Expand Down
1 change: 1 addition & 0 deletions pkg/website/website_routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const (
EventMovie EventType = "movie"
EventEpisode EventType = "episode"
EventPoll EventType = "poll"
EventCheck EventType = "upcheck"
EventSignal EventType = "signal"
EventFile EventType = "file"
EventSet EventType = "setStates"
Expand Down

0 comments on commit 8b11430

Please sign in to comment.