mirror of
https://github.com/idanoo/autobrr
synced 2025-07-23 16:59:12 +00:00
feat: show new updates in dashboard (#690)
* feat: show new update banner * feat(http): add request logger * refactor: updates checker * feat: make update check optional * fix: empty releases * add toggle switch for update checks * feat: toggle updates check from settings * feat: toggle updates check from settings * feat: check on toggle enabled --------- Co-authored-by: soup <soup@r4tio.dev>
This commit is contained in:
parent
3fdd7cf5e4
commit
2917a7d42d
24 changed files with 687 additions and 121 deletions
|
@ -2,6 +2,7 @@ package config
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
|
@ -72,6 +73,10 @@ logLevel = "DEBUG"
|
|||
#
|
||||
#logMaxBackups = 3
|
||||
|
||||
# Check for updates
|
||||
#
|
||||
checkForUpdates = true
|
||||
|
||||
# Session secret
|
||||
#
|
||||
sessionSecret = "{{ .sessionSecret }}"
|
||||
|
@ -148,6 +153,7 @@ func writeConfig(configPath string, configFile string) error {
|
|||
}
|
||||
|
||||
type Config interface {
|
||||
UpdateConfig() error
|
||||
DynamicReload(log logger.Logger)
|
||||
}
|
||||
|
||||
|
@ -179,6 +185,7 @@ func (c *AppConfig) defaults() {
|
|||
BaseURL: "/",
|
||||
SessionSecret: "secret-session-key",
|
||||
CustomDefinitions: "",
|
||||
CheckForUpdates: true,
|
||||
DatabaseType: "sqlite",
|
||||
PostgresHost: "",
|
||||
PostgresPort: 0,
|
||||
|
@ -240,6 +247,9 @@ func (c *AppConfig) DynamicReload(log logger.Logger) {
|
|||
logPath := viper.GetString("logPath")
|
||||
c.Config.LogPath = logPath
|
||||
|
||||
checkUpdates := viper.GetBool("checkForUpdates")
|
||||
c.Config.CheckForUpdates = checkUpdates
|
||||
|
||||
log.Debug().Msg("config file reloaded!")
|
||||
|
||||
c.m.Unlock()
|
||||
|
@ -248,3 +258,46 @@ func (c *AppConfig) DynamicReload(log logger.Logger) {
|
|||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *AppConfig) UpdateConfig() error {
|
||||
file := path.Join(c.Config.ConfigPath, "config.toml")
|
||||
|
||||
f, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not read config file: %s", file)
|
||||
}
|
||||
|
||||
lines := strings.Split(string(f), "\n")
|
||||
lines = c.processLines(lines)
|
||||
|
||||
output := strings.Join(lines, "\n")
|
||||
if err := os.WriteFile(file, []byte(output), 0644); err != nil {
|
||||
return errors.Wrap(err, "could not write config file: %s", file)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *AppConfig) processLines(lines []string) []string {
|
||||
// keep track of not found values to append at bottom
|
||||
var (
|
||||
foundLineUpdate = false
|
||||
)
|
||||
|
||||
for i, line := range lines {
|
||||
// set checkForUpdates
|
||||
if !foundLineUpdate && strings.Contains(line, "checkForUpdates =") {
|
||||
lines[i] = fmt.Sprintf("checkForUpdates = %t", c.Config.CheckForUpdates)
|
||||
foundLineUpdate = true
|
||||
}
|
||||
}
|
||||
|
||||
// append missing vars to bottom
|
||||
if !foundLineUpdate {
|
||||
lines = append(lines, "# Check for updates")
|
||||
lines = append(lines, "#")
|
||||
lines = append(lines, fmt.Sprintf("checkForUpdates = %t", c.Config.CheckForUpdates))
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
|
55
internal/config/config_test.go
Normal file
55
internal/config/config_test.go
Normal file
|
@ -0,0 +1,55 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/autobrr/autobrr/internal/domain"
|
||||
)
|
||||
|
||||
func TestAppConfig_processLines(t *testing.T) {
|
||||
type fields struct {
|
||||
Config *domain.Config
|
||||
m sync.Mutex
|
||||
}
|
||||
type args struct {
|
||||
lines []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "append missing",
|
||||
fields: fields{
|
||||
Config: &domain.Config{CheckForUpdates: true},
|
||||
m: sync.Mutex{},
|
||||
},
|
||||
args: args{[]string{}},
|
||||
want: []string{"# Check for updates", "#", "checkForUpdates = true"},
|
||||
},
|
||||
{
|
||||
name: "update existing",
|
||||
fields: fields{
|
||||
Config: &domain.Config{CheckForUpdates: true},
|
||||
m: sync.Mutex{},
|
||||
},
|
||||
args: args{[]string{"# Check for updates", "#", "#checkForUpdates = false"}},
|
||||
want: []string{"# Check for updates", "#", "checkForUpdates = true"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &AppConfig{
|
||||
Config: tt.fields.Config,
|
||||
m: tt.fields.m,
|
||||
}
|
||||
if got := c.processLines(tt.args.lines); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("processLines() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@ type Config struct {
|
|||
BaseURL string `toml:"baseUrl"`
|
||||
SessionSecret string `toml:"sessionSecret"`
|
||||
CustomDefinitions string `toml:"customDefinitions"`
|
||||
CheckForUpdates bool `toml:"checkForUpdates"`
|
||||
DatabaseType string `toml:"databaseType"`
|
||||
PostgresHost string `toml:"postgresHost"`
|
||||
PostgresPort int `toml:"postgresPort"`
|
||||
|
@ -19,3 +20,12 @@ type Config struct {
|
|||
PostgresUser string `toml:"postgresUser"`
|
||||
PostgresPass string `toml:"postgresPass"`
|
||||
}
|
||||
|
||||
type ConfigUpdate struct {
|
||||
Host *string `json:"host,omitempty"`
|
||||
Port *int `json:"port,omitempty"`
|
||||
LogLevel *string `json:"log_level,omitempty"`
|
||||
LogPath *string `json:"log_path,omitempty"`
|
||||
BaseURL *string `json:"base_url,omitempty"`
|
||||
CheckForUpdates *bool `json:"check_for_updates,omitempty"`
|
||||
}
|
||||
|
|
|
@ -1,52 +1,84 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/autobrr/autobrr/internal/config"
|
||||
"github.com/autobrr/autobrr/internal/domain"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
type configJson struct {
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
LogLevel string `json:"log_level"`
|
||||
LogPath string `json:"log_path"`
|
||||
BaseURL string `json:"base_url"`
|
||||
Version string `json:"version"`
|
||||
Commit string `json:"commit"`
|
||||
Date string `json:"date"`
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
LogLevel string `json:"log_level"`
|
||||
LogPath string `json:"log_path"`
|
||||
BaseURL string `json:"base_url"`
|
||||
CheckForUpdates bool `json:"check_for_updates"`
|
||||
Version string `json:"version"`
|
||||
Commit string `json:"commit"`
|
||||
Date string `json:"date"`
|
||||
}
|
||||
|
||||
type configHandler struct {
|
||||
encoder encoder
|
||||
|
||||
cfg *config.AppConfig
|
||||
server Server
|
||||
}
|
||||
|
||||
func newConfigHandler(encoder encoder, server Server) *configHandler {
|
||||
func newConfigHandler(encoder encoder, server Server, cfg *config.AppConfig) *configHandler {
|
||||
return &configHandler{
|
||||
encoder: encoder,
|
||||
cfg: cfg,
|
||||
server: server,
|
||||
}
|
||||
}
|
||||
|
||||
func (h configHandler) Routes(r chi.Router) {
|
||||
r.Get("/", h.getConfig)
|
||||
r.Patch("/", h.updateConfig)
|
||||
}
|
||||
|
||||
func (h configHandler) getConfig(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
conf := configJson{
|
||||
Host: h.server.config.Host,
|
||||
Port: h.server.config.Port,
|
||||
LogLevel: h.server.config.LogLevel,
|
||||
LogPath: h.server.config.LogPath,
|
||||
BaseURL: h.server.config.BaseURL,
|
||||
Version: h.server.version,
|
||||
Commit: h.server.commit,
|
||||
Date: h.server.date,
|
||||
Host: h.cfg.Config.Host,
|
||||
Port: h.cfg.Config.Port,
|
||||
LogLevel: h.cfg.Config.LogLevel,
|
||||
LogPath: h.cfg.Config.LogPath,
|
||||
BaseURL: h.cfg.Config.BaseURL,
|
||||
CheckForUpdates: h.cfg.Config.CheckForUpdates,
|
||||
Version: h.server.version,
|
||||
Commit: h.server.commit,
|
||||
Date: h.server.date,
|
||||
}
|
||||
|
||||
h.encoder.StatusResponse(ctx, w, conf, http.StatusOK)
|
||||
render.JSON(w, r, conf)
|
||||
}
|
||||
|
||||
func (h configHandler) updateConfig(w http.ResponseWriter, r *http.Request) {
|
||||
var data domain.ConfigUpdate
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
|
||||
h.encoder.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if data.CheckForUpdates != nil {
|
||||
h.cfg.Config.CheckForUpdates = *data.CheckForUpdates
|
||||
}
|
||||
|
||||
if err := h.cfg.UpdateConfig(); err != nil {
|
||||
render.Status(r, http.StatusInternalServerError)
|
||||
render.JSON(w, r, errorResponse{
|
||||
Message: err.Error(),
|
||||
Status: http.StatusInternalServerError,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
render.NoContent(w, r)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
package http
|
||||
|
||||
import "net/http"
|
||||
import (
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
func (s Server) IsAuthenticated(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -31,3 +38,49 @@ func (s Server) IsAuthenticated(next http.Handler) http.Handler {
|
|||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func LoggerMiddleware(logger *zerolog.Logger) func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
log := logger.With().Logger()
|
||||
|
||||
ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor)
|
||||
|
||||
t1 := time.Now()
|
||||
defer func() {
|
||||
t2 := time.Now()
|
||||
|
||||
// Recover and record stack traces in case of a panic
|
||||
if rec := recover(); rec != nil {
|
||||
log.Error().
|
||||
Str("type", "error").
|
||||
Timestamp().
|
||||
Interface("recover_info", rec).
|
||||
Bytes("debug_stack", debug.Stack()).
|
||||
Msg("log system error")
|
||||
http.Error(ww, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
// log end request
|
||||
log.Info().
|
||||
Str("type", "access").
|
||||
Timestamp().
|
||||
Fields(map[string]interface{}{
|
||||
"remote_ip": r.RemoteAddr,
|
||||
"url": r.URL.Path,
|
||||
"proto": r.Proto,
|
||||
"method": r.Method,
|
||||
"user_agent": r.Header.Get("User-Agent"),
|
||||
"status": ww.Status(),
|
||||
"latency_ms": float64(t2.Sub(t1).Nanoseconds()) / 1000000.0,
|
||||
"bytes_in": r.Header.Get("Content-Length"),
|
||||
"bytes_out": ww.BytesWritten(),
|
||||
}).
|
||||
Msg("incoming_request")
|
||||
}()
|
||||
|
||||
next.ServeHTTP(ww, r)
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,8 +6,8 @@ import (
|
|||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/autobrr/autobrr/internal/config"
|
||||
"github.com/autobrr/autobrr/internal/database"
|
||||
"github.com/autobrr/autobrr/internal/domain"
|
||||
"github.com/autobrr/autobrr/internal/logger"
|
||||
"github.com/autobrr/autobrr/web"
|
||||
|
||||
|
@ -24,7 +24,7 @@ type Server struct {
|
|||
sse *sse.Server
|
||||
db *database.DB
|
||||
|
||||
config *domain.Config
|
||||
config *config.AppConfig
|
||||
cookieStore *sessions.CookieStore
|
||||
|
||||
version string
|
||||
|
@ -41,9 +41,10 @@ type Server struct {
|
|||
ircService ircService
|
||||
notificationService notificationService
|
||||
releaseService releaseService
|
||||
updateService updateService
|
||||
}
|
||||
|
||||
func NewServer(log logger.Logger, config *domain.Config, sse *sse.Server, db *database.DB, version string, commit string, date string, actionService actionService, apiService apikeyService, authService authService, downloadClientSvc downloadClientService, filterSvc filterService, feedSvc feedService, indexerSvc indexerService, ircSvc ircService, notificationSvc notificationService, releaseSvc releaseService) Server {
|
||||
func NewServer(log logger.Logger, config *config.AppConfig, sse *sse.Server, db *database.DB, version string, commit string, date string, actionService actionService, apiService apikeyService, authService authService, downloadClientSvc downloadClientService, filterSvc filterService, feedSvc feedService, indexerSvc indexerService, ircSvc ircService, notificationSvc notificationService, releaseSvc releaseService, updateSvc updateService) Server {
|
||||
return Server{
|
||||
log: log.With().Str("module", "http").Logger(),
|
||||
config: config,
|
||||
|
@ -53,7 +54,7 @@ func NewServer(log logger.Logger, config *domain.Config, sse *sse.Server, db *da
|
|||
commit: commit,
|
||||
date: date,
|
||||
|
||||
cookieStore: sessions.NewCookieStore([]byte(config.SessionSecret)),
|
||||
cookieStore: sessions.NewCookieStore([]byte(config.Config.SessionSecret)),
|
||||
|
||||
actionService: actionService,
|
||||
apiService: apiService,
|
||||
|
@ -65,11 +66,12 @@ func NewServer(log logger.Logger, config *domain.Config, sse *sse.Server, db *da
|
|||
ircService: ircSvc,
|
||||
notificationService: notificationSvc,
|
||||
releaseService: releaseSvc,
|
||||
updateService: updateSvc,
|
||||
}
|
||||
}
|
||||
|
||||
func (s Server) Open() error {
|
||||
addr := fmt.Sprintf("%v:%v", s.config.Host, s.config.Port)
|
||||
addr := fmt.Sprintf("%v:%v", s.config.Config.Host, s.config.Config.Port)
|
||||
listener, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -79,6 +81,8 @@ func (s Server) Open() error {
|
|||
Handler: s.Handler(),
|
||||
}
|
||||
|
||||
s.log.Info().Msgf("Starting server. Listening on %s", listener.Addr().String())
|
||||
|
||||
return server.Serve(listener)
|
||||
}
|
||||
|
||||
|
@ -88,6 +92,7 @@ func (s Server) Handler() http.Handler {
|
|||
r.Use(middleware.RequestID)
|
||||
r.Use(middleware.RealIP)
|
||||
r.Use(middleware.Recoverer)
|
||||
r.Use(LoggerMiddleware(&s.log))
|
||||
|
||||
c := cors.New(cors.Options{
|
||||
AllowCredentials: true,
|
||||
|
@ -113,15 +118,15 @@ func (s Server) Handler() http.Handler {
|
|||
fileSystem.ServeHTTP(w, r)
|
||||
})
|
||||
|
||||
r.Route("/api/auth", newAuthHandler(encoder, s.log, s.config, s.cookieStore, s.authService).Routes)
|
||||
r.Route("/api/healthz", newHealthHandler(encoder, s.db).Routes)
|
||||
r.Route("/api", func(r chi.Router) {
|
||||
r.Route("/auth", newAuthHandler(encoder, s.log, s.config.Config, s.cookieStore, s.authService).Routes)
|
||||
r.Route("/healthz", newHealthHandler(encoder, s.db).Routes)
|
||||
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(s.IsAuthenticated)
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(s.IsAuthenticated)
|
||||
|
||||
r.Route("/api", func(r chi.Router) {
|
||||
r.Route("/actions", newActionHandler(encoder, s.actionService).Routes)
|
||||
r.Route("/config", newConfigHandler(encoder, s).Routes)
|
||||
r.Route("/config", newConfigHandler(encoder, s, s.config).Routes)
|
||||
r.Route("/download_clients", newDownloadClientHandler(encoder, s.downloadClientService).Routes)
|
||||
r.Route("/filters", newFilterHandler(encoder, s.filterService).Routes)
|
||||
r.Route("/feeds", newFeedHandler(encoder, s.feedService).Routes)
|
||||
|
@ -130,6 +135,7 @@ func (s Server) Handler() http.Handler {
|
|||
r.Route("/keys", newAPIKeyHandler(encoder, s.apiService).Routes)
|
||||
r.Route("/notification", newNotificationHandler(encoder, s.notificationService).Routes)
|
||||
r.Route("/release", newReleaseHandler(encoder, s.releaseService).Routes)
|
||||
r.Route("/updates", newUpdateHandler(encoder, s.updateService).Routes)
|
||||
|
||||
r.HandleFunc("/events", func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
|
@ -157,7 +163,7 @@ func (s Server) index(w http.ResponseWriter, r *http.Request) {
|
|||
p := web.IndexParams{
|
||||
Title: "Dashboard",
|
||||
Version: s.version,
|
||||
BaseUrl: s.config.BaseURL,
|
||||
BaseUrl: s.config.Config.BaseURL,
|
||||
}
|
||||
web.Index(w, p)
|
||||
}
|
||||
|
|
50
internal/http/update.go
Normal file
50
internal/http/update.go
Normal file
|
@ -0,0 +1,50 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/autobrr/autobrr/pkg/version"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
type updateService interface {
|
||||
CheckUpdates(ctx context.Context)
|
||||
GetLatestRelease(ctx context.Context) *version.Release
|
||||
}
|
||||
|
||||
type updateHandler struct {
|
||||
encoder encoder
|
||||
service updateService
|
||||
}
|
||||
|
||||
func newUpdateHandler(encoder encoder, service updateService) *updateHandler {
|
||||
return &updateHandler{
|
||||
encoder: encoder,
|
||||
service: service,
|
||||
}
|
||||
}
|
||||
|
||||
func (h updateHandler) Routes(r chi.Router) {
|
||||
r.Get("/latest", h.getLatest)
|
||||
r.Get("/check", h.checkUpdates)
|
||||
}
|
||||
|
||||
func (h updateHandler) getLatest(w http.ResponseWriter, r *http.Request) {
|
||||
latest := h.service.GetLatestRelease(r.Context())
|
||||
if latest != nil {
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, latest)
|
||||
return
|
||||
}
|
||||
|
||||
render.NoContent(w, r)
|
||||
}
|
||||
|
||||
func (h updateHandler) checkUpdates(w http.ResponseWriter, r *http.Request) {
|
||||
h.service.CheckUpdates(r.Context())
|
||||
|
||||
render.NoContent(w, r)
|
||||
}
|
|
@ -6,46 +6,42 @@ import (
|
|||
|
||||
"github.com/autobrr/autobrr/internal/domain"
|
||||
"github.com/autobrr/autobrr/internal/notification"
|
||||
"github.com/autobrr/autobrr/pkg/version"
|
||||
"github.com/autobrr/autobrr/internal/update"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
type CheckUpdatesJob struct {
|
||||
Name string
|
||||
Log zerolog.Logger
|
||||
Version string
|
||||
NotifSvc notification.Service
|
||||
Name string
|
||||
Log zerolog.Logger
|
||||
Version string
|
||||
NotifSvc notification.Service
|
||||
updateService *update.Service
|
||||
|
||||
lastCheckVersion string
|
||||
}
|
||||
|
||||
func (j *CheckUpdatesJob) Run() {
|
||||
v := version.Checker{
|
||||
Owner: "autobrr",
|
||||
Repo: "autobrr",
|
||||
}
|
||||
|
||||
newAvailable, newVersion, err := v.CheckNewVersion(context.TODO(), j.Version)
|
||||
newRelease, err := j.updateService.CheckUpdateAvailable(context.TODO())
|
||||
if err != nil {
|
||||
j.Log.Error().Err(err).Msg("could not check for new release")
|
||||
return
|
||||
}
|
||||
|
||||
if newAvailable {
|
||||
j.Log.Info().Msgf("a new release has been found: %v Consider updating.", newVersion)
|
||||
|
||||
if newRelease != nil {
|
||||
// this is not persisted so this can trigger more than once
|
||||
// lets check if we have different versions between runs
|
||||
if newVersion != j.lastCheckVersion {
|
||||
if newRelease.TagName != j.lastCheckVersion {
|
||||
j.Log.Info().Msgf("a new release has been found: %v Consider updating.", newRelease.TagName)
|
||||
|
||||
j.NotifSvc.Send(domain.NotificationEventAppUpdateAvailable, domain.NotificationPayload{
|
||||
Subject: "New update available!",
|
||||
Message: newVersion,
|
||||
Message: newRelease.TagName,
|
||||
Event: domain.NotificationEventAppUpdateAvailable,
|
||||
Timestamp: time.Now(),
|
||||
})
|
||||
}
|
||||
|
||||
j.lastCheckVersion = newVersion
|
||||
j.lastCheckVersion = newRelease.TagName
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,10 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/autobrr/autobrr/internal/domain"
|
||||
"github.com/autobrr/autobrr/internal/logger"
|
||||
"github.com/autobrr/autobrr/internal/notification"
|
||||
"github.com/autobrr/autobrr/internal/update"
|
||||
|
||||
"github.com/robfig/cron/v3"
|
||||
"github.com/rs/zerolog"
|
||||
|
@ -21,19 +23,22 @@ type Service interface {
|
|||
|
||||
type service struct {
|
||||
log zerolog.Logger
|
||||
config *domain.Config
|
||||
version string
|
||||
notificationSvc notification.Service
|
||||
updateSvc *update.Service
|
||||
|
||||
cron *cron.Cron
|
||||
jobs map[string]cron.EntryID
|
||||
m sync.RWMutex
|
||||
}
|
||||
|
||||
func NewService(log logger.Logger, version string, notificationSvc notification.Service) Service {
|
||||
func NewService(log logger.Logger, config *domain.Config, notificationSvc notification.Service, updateSvc *update.Service) Service {
|
||||
return &service{
|
||||
log: log.With().Str("module", "scheduler").Logger(),
|
||||
version: version,
|
||||
config: config,
|
||||
notificationSvc: notificationSvc,
|
||||
updateSvc: updateSvc,
|
||||
cron: cron.New(cron.WithChain(
|
||||
cron.Recover(cron.DefaultLogger),
|
||||
)),
|
||||
|
@ -56,16 +61,19 @@ func (s *service) Start() {
|
|||
func (s *service) addAppJobs() {
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
checkUpdates := &CheckUpdatesJob{
|
||||
Name: "app-check-updates",
|
||||
Log: s.log.With().Str("job", "app-check-updates").Logger(),
|
||||
Version: s.version,
|
||||
NotifSvc: s.notificationSvc,
|
||||
lastCheckVersion: "",
|
||||
}
|
||||
if s.config.CheckForUpdates {
|
||||
checkUpdates := &CheckUpdatesJob{
|
||||
Name: "app-check-updates",
|
||||
Log: s.log.With().Str("job", "app-check-updates").Logger(),
|
||||
Version: s.version,
|
||||
NotifSvc: s.notificationSvc,
|
||||
updateService: s.updateSvc,
|
||||
lastCheckVersion: s.version,
|
||||
}
|
||||
|
||||
if id, err := s.AddJob(checkUpdates, time.Duration(36*time.Hour), "app-check-updates"); err != nil {
|
||||
s.log.Error().Err(err).Msgf("scheduler.addAppJobs: error adding job: %v", id)
|
||||
if id, err := s.AddJob(checkUpdates, 2*time.Hour, "app-check-updates"); err != nil {
|
||||
s.log.Error().Err(err).Msgf("scheduler.addAppJobs: error adding job: %v", id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,7 +89,7 @@ func (s *service) AddJob(job cron.Job, interval time.Duration, identifier string
|
|||
cron.SkipIfStillRunning(cron.DiscardLogger)).Then(job),
|
||||
)
|
||||
|
||||
s.log.Debug().Msgf("scheduler.AddJob: job successfully added: %v", id)
|
||||
s.log.Debug().Msgf("scheduler.AddJob: job successfully added: %s id %d", identifier, id)
|
||||
|
||||
s.m.Lock()
|
||||
// add to job map
|
||||
|
|
|
@ -1,43 +1,49 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"github.com/autobrr/autobrr/internal/domain"
|
||||
"github.com/autobrr/autobrr/internal/feed"
|
||||
"github.com/autobrr/autobrr/internal/indexer"
|
||||
"github.com/autobrr/autobrr/internal/irc"
|
||||
"github.com/autobrr/autobrr/internal/logger"
|
||||
"github.com/autobrr/autobrr/internal/scheduler"
|
||||
"github.com/autobrr/autobrr/internal/update"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
log zerolog.Logger
|
||||
Hostname string
|
||||
Port int
|
||||
log zerolog.Logger
|
||||
config *domain.Config
|
||||
|
||||
indexerService indexer.Service
|
||||
ircService irc.Service
|
||||
feedService feed.Service
|
||||
scheduler scheduler.Service
|
||||
updateService *update.Service
|
||||
|
||||
stopWG sync.WaitGroup
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func NewServer(log logger.Logger, ircSvc irc.Service, indexerSvc indexer.Service, feedSvc feed.Service, scheduler scheduler.Service) *Server {
|
||||
func NewServer(log logger.Logger, config *domain.Config, ircSvc irc.Service, indexerSvc indexer.Service, feedSvc feed.Service, scheduler scheduler.Service, updateSvc *update.Service) *Server {
|
||||
return &Server{
|
||||
log: log.With().Str("module", "server").Logger(),
|
||||
config: config,
|
||||
indexerService: indexerSvc,
|
||||
ircService: ircSvc,
|
||||
feedService: feedSvc,
|
||||
scheduler: scheduler,
|
||||
updateService: updateSvc,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) Start() error {
|
||||
s.log.Info().Msgf("Starting server. Listening on %v:%v", s.Hostname, s.Port)
|
||||
go s.checkUpdates()
|
||||
|
||||
// start cron scheduler
|
||||
s.scheduler.Start()
|
||||
|
@ -68,3 +74,11 @@ func (s *Server) Shutdown() {
|
|||
// stop cron scheduler
|
||||
s.scheduler.Stop()
|
||||
}
|
||||
|
||||
func (s *Server) checkUpdates() {
|
||||
if s.config.CheckForUpdates {
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
s.updateService.CheckUpdates(context.Background())
|
||||
}
|
||||
}
|
||||
|
|
71
internal/update/update.go
Normal file
71
internal/update/update.go
Normal file
|
@ -0,0 +1,71 @@
|
|||
package update
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/autobrr/autobrr/internal/domain"
|
||||
"github.com/autobrr/autobrr/internal/logger"
|
||||
"github.com/autobrr/autobrr/pkg/version"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
log zerolog.Logger
|
||||
config *domain.Config
|
||||
|
||||
m sync.RWMutex
|
||||
releaseChecker *version.Checker
|
||||
latestRelease *version.Release
|
||||
}
|
||||
|
||||
func NewUpdate(log logger.Logger, config *domain.Config) *Service {
|
||||
return &Service{
|
||||
log: log.With().Str("module", "update").Logger(),
|
||||
config: config,
|
||||
releaseChecker: version.NewChecker("autobrr", "autobrr", config.Version),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) GetLatestRelease(ctx context.Context) *version.Release {
|
||||
s.m.RLock()
|
||||
defer s.m.RUnlock()
|
||||
return s.latestRelease
|
||||
}
|
||||
|
||||
func (s *Service) CheckUpdates(ctx context.Context) {
|
||||
if _, err := s.CheckUpdateAvailable(ctx); err != nil {
|
||||
s.log.Error().Err(err).Msg("error checking new release")
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Service) CheckUpdateAvailable(ctx context.Context) (*version.Release, error) {
|
||||
s.log.Trace().Msg("checking for updates...")
|
||||
|
||||
newAvailable, newVersion, err := s.releaseChecker.CheckNewVersion(ctx, s.config.Version)
|
||||
if err != nil {
|
||||
s.log.Error().Err(err).Msg("could not check for new release")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if newAvailable {
|
||||
s.log.Info().Msgf("autobrr outdated, found newer release: %s", newVersion.TagName)
|
||||
|
||||
s.m.Lock()
|
||||
defer s.m.Unlock()
|
||||
|
||||
if s.latestRelease != nil && s.latestRelease.TagName == newVersion.TagName {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
s.latestRelease = newVersion
|
||||
|
||||
return newVersion, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue