forked from gsn/predictor
feat: downloader
This commit is contained in:
parent
b9c1a98895
commit
42e7924be9
37 changed files with 2422 additions and 94 deletions
21
pkg/redis/interface.go
Normal file
21
pkg/redis/interface.go
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Service defines the interface for Redis operations
|
||||
type Service interface {
|
||||
// Lock acquires a distributed lock
|
||||
Lock(ctx context.Context, key string, ttl time.Duration) (func(context.Context), error)
|
||||
|
||||
// Set sets a key with value and TTL
|
||||
Set(key string, value []byte, ttl time.Duration) error
|
||||
|
||||
// Get retrieves a value by key
|
||||
Get(key string) ([]byte, error)
|
||||
|
||||
// Close closes the Redis connection
|
||||
Close() error
|
||||
}
|
||||
89
pkg/redis/redis.go
Normal file
89
pkg/redis/redis.go
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.intra.yksa.space/gsn/predictor/internal/pkg/errcodes"
|
||||
"github.com/bsm/redislock"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
client *redis.Client
|
||||
locker *redislock.Client
|
||||
}
|
||||
|
||||
// Ensure Client implements Service interface
|
||||
var _ Service = (*Client)(nil)
|
||||
|
||||
type Config struct {
|
||||
Host string
|
||||
Port int
|
||||
Password string
|
||||
DB int
|
||||
}
|
||||
|
||||
func New(cfg Config) (*Client, error) {
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Addr: fmt.Sprintf("%s:%d", cfg.Host, cfg.Port),
|
||||
Password: cfg.Password,
|
||||
DB: cfg.DB,
|
||||
})
|
||||
|
||||
// Test connection
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := client.Ping(ctx).Err(); err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to redis: %w", err)
|
||||
}
|
||||
|
||||
locker := redislock.New(client)
|
||||
|
||||
return &Client{
|
||||
client: client,
|
||||
locker: locker,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) Lock(ctx context.Context, key string, ttl time.Duration) (func(context.Context), error) {
|
||||
lock, err := c.locker.Obtain(ctx, key, ttl, nil)
|
||||
if err != nil {
|
||||
if err == redislock.ErrNotObtained {
|
||||
return nil, errcodes.ErrRedisLockAlreadyLocked
|
||||
}
|
||||
return nil, errcodes.Wrap(err, "failed to obtain redis lock")
|
||||
}
|
||||
|
||||
unlock := func(ctx context.Context) {
|
||||
lock.Release(ctx)
|
||||
}
|
||||
|
||||
return unlock, nil
|
||||
}
|
||||
|
||||
func (c *Client) Set(key string, value []byte, ttl time.Duration) error {
|
||||
ctx := context.Background()
|
||||
if err := c.client.Set(ctx, key, value, ttl).Err(); err != nil {
|
||||
return errcodes.Wrap(err, "failed to set redis key")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) Get(key string) ([]byte, error) {
|
||||
ctx := context.Background()
|
||||
result := c.client.Get(ctx, key)
|
||||
if result.Err() != nil {
|
||||
if result.Err() == redis.Nil {
|
||||
return nil, errcodes.ErrRedisCacheMiss
|
||||
}
|
||||
return nil, errcodes.Wrap(result.Err(), "failed to get redis key")
|
||||
}
|
||||
return result.Bytes()
|
||||
}
|
||||
|
||||
func (c *Client) Close() error {
|
||||
return c.client.Close()
|
||||
}
|
||||
5
pkg/scheduler/config.go
Normal file
5
pkg/scheduler/config.go
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
package scheduler
|
||||
|
||||
type Config struct {
|
||||
Enabled bool `env:"ENABLED" envDefault:"true"`
|
||||
}
|
||||
99
pkg/scheduler/scheduler.go
Normal file
99
pkg/scheduler/scheduler.go
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
package scheduler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"git.intra.yksa.space/gsn/predictor/internal/pkg/errcodes"
|
||||
"github.com/go-co-op/gocron"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type Job interface {
|
||||
GetInterval() time.Duration
|
||||
GetTimeout() time.Duration
|
||||
GetCount() int
|
||||
GetAsync() bool
|
||||
Execute(context.Context) error
|
||||
}
|
||||
|
||||
type Scheduler struct {
|
||||
scheduler *gocron.Scheduler
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
func New(logger *zap.Logger) *Scheduler {
|
||||
scheduler := gocron.NewScheduler(time.UTC)
|
||||
|
||||
return &Scheduler{
|
||||
scheduler: scheduler,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Scheduler) AddJob(job Job) error {
|
||||
interval := job.GetInterval()
|
||||
timeout := job.GetTimeout()
|
||||
count := job.GetCount()
|
||||
async := job.GetAsync()
|
||||
|
||||
// Validate job parameters
|
||||
if !async && count != 1 {
|
||||
return errcodes.ErrSchedulerInvalidJob
|
||||
}
|
||||
if timeout > interval {
|
||||
return errcodes.ErrSchedulerTimeoutTooLong
|
||||
}
|
||||
|
||||
// Create job function with timeout
|
||||
jobFunc := func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
if err := job.Execute(ctx); err != nil {
|
||||
s.logger.Error("job execution failed",
|
||||
zap.Error(err),
|
||||
zap.Duration("interval", interval),
|
||||
zap.Duration("timeout", timeout))
|
||||
} else {
|
||||
s.logger.Debug("job executed successfully",
|
||||
zap.Duration("interval", interval),
|
||||
zap.Duration("timeout", timeout))
|
||||
}
|
||||
}
|
||||
|
||||
// Add job to scheduler
|
||||
schedulerJob := s.scheduler.Every(interval)
|
||||
|
||||
if !async {
|
||||
schedulerJob = schedulerJob.SingletonMode()
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
schedulerJob = schedulerJob.LimitRunsTo(count)
|
||||
}
|
||||
|
||||
schedulerJob.Do(jobFunc)
|
||||
|
||||
s.logger.Info("job added to scheduler",
|
||||
zap.Duration("interval", interval),
|
||||
zap.Duration("timeout", timeout),
|
||||
zap.Int("count", count),
|
||||
zap.Bool("async", async))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Scheduler) Start() {
|
||||
s.scheduler.StartAsync()
|
||||
s.logger.Info("scheduler started")
|
||||
}
|
||||
|
||||
func (s *Scheduler) Stop() {
|
||||
s.scheduler.Stop()
|
||||
s.logger.Info("scheduler stopped")
|
||||
}
|
||||
|
||||
func (s *Scheduler) IsRunning() bool {
|
||||
return s.scheduler.IsRunning()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue