2025-10-02 20:35:53 +03:00
|
|
|
package app
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
log "github.com/sirupsen/logrus"
|
2026-02-28 10:53:33 +03:00
|
|
|
"google.golang.org/grpc"
|
2025-10-02 20:35:53 +03:00
|
|
|
"net"
|
|
|
|
|
"runtime"
|
2025-10-03 19:17:01 +03:00
|
|
|
"task-processor/config"
|
|
|
|
|
"task-processor/internal/appState"
|
|
|
|
|
"task-processor/internal/parsers"
|
2025-12-26 16:19:09 +03:00
|
|
|
"task-processor/internal/parsers/mandarake"
|
2025-10-03 19:17:01 +03:00
|
|
|
"task-processor/internal/processor"
|
2025-12-20 16:05:56 +03:00
|
|
|
"task-processor/internal/remote"
|
2025-10-03 19:17:01 +03:00
|
|
|
"task-processor/internal/shared"
|
2026-02-17 12:19:47 +03:00
|
|
|
"task-processor/pkg/router"
|
2025-10-02 20:35:53 +03:00
|
|
|
"time"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type App struct {
|
2025-10-03 19:17:01 +03:00
|
|
|
config *config.Config
|
|
|
|
|
checkPeriod time.Duration
|
|
|
|
|
startTime time.Time
|
|
|
|
|
retryCount int
|
|
|
|
|
retryMinutes int
|
|
|
|
|
state *appState.State
|
2025-12-20 16:05:56 +03:00
|
|
|
network *remote.Network
|
2025-10-03 19:17:01 +03:00
|
|
|
numCPUs int
|
2026-02-28 10:53:33 +03:00
|
|
|
metricsSrv *router.Handler
|
|
|
|
|
taskApiSrv *grpc.Server
|
2025-10-02 20:35:53 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func New(c *config.Config) *App {
|
|
|
|
|
numCPUs := c.NumCPUs
|
|
|
|
|
if numCPUs < 1 {
|
|
|
|
|
numCPUs = runtime.NumCPU()
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-03 19:17:01 +03:00
|
|
|
st := appState.NewState(numCPUs, c.CheckPeriod, c.TasksCfg.RetryCount, c.TasksCfg.RetryMinutes)
|
2025-10-02 20:35:53 +03:00
|
|
|
|
2026-02-28 10:53:33 +03:00
|
|
|
server := newServer(st)
|
|
|
|
|
|
|
|
|
|
//metrics
|
|
|
|
|
mSrv := router.NewHandler(router.Deps{
|
|
|
|
|
Addr: net.JoinHostPort(c.Metrics.Host, c.Metrics.Port),
|
|
|
|
|
GinMode: c.Metrics.GinMode,
|
|
|
|
|
})
|
|
|
|
|
|
2025-10-02 20:35:53 +03:00
|
|
|
return &App{
|
2025-10-03 19:17:01 +03:00
|
|
|
config: c,
|
|
|
|
|
checkPeriod: time.Duration(c.CheckPeriod),
|
|
|
|
|
startTime: time.Now(),
|
|
|
|
|
retryCount: c.TasksCfg.RetryCount,
|
|
|
|
|
retryMinutes: c.TasksCfg.RetryMinutes,
|
|
|
|
|
state: st,
|
2025-12-20 16:05:56 +03:00
|
|
|
network: remote.NewHandler(),
|
2025-10-03 19:17:01 +03:00
|
|
|
numCPUs: numCPUs,
|
2026-02-28 10:53:33 +03:00
|
|
|
metricsSrv: mSrv,
|
|
|
|
|
taskApiSrv: server,
|
2025-10-02 20:35:53 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-28 10:53:33 +03:00
|
|
|
func (app *App) Run(ctx context.Context) {
|
2025-10-02 20:35:53 +03:00
|
|
|
log.Info("Application start")
|
2026-02-28 10:53:33 +03:00
|
|
|
|
|
|
|
|
addr := net.JoinHostPort(app.config.GrpcCfg.ServerHost, app.config.GrpcCfg.ServerPort)
|
|
|
|
|
|
2025-10-02 20:35:53 +03:00
|
|
|
log.WithFields(log.Fields{
|
2026-02-28 10:53:33 +03:00
|
|
|
"Service address": addr,
|
2025-10-03 19:17:01 +03:00
|
|
|
"Number of CPUs": app.numCPUs,
|
2025-10-02 20:35:53 +03:00
|
|
|
}).Debug("App settings")
|
|
|
|
|
|
2026-02-28 10:53:33 +03:00
|
|
|
errChan := make(chan error, 16)
|
2026-02-17 12:19:47 +03:00
|
|
|
|
|
|
|
|
//main
|
2025-10-03 19:17:01 +03:00
|
|
|
apiClient := newApiClient(app.config.GrpcCfg.ApiClientHost + ":" + app.config.GrpcCfg.ApiClientPort)
|
2025-10-02 20:35:53 +03:00
|
|
|
|
2025-10-03 19:17:01 +03:00
|
|
|
sender := make(chan shared.TaskResult, app.numCPUs*10)
|
2026-02-28 10:53:33 +03:00
|
|
|
defer close(sender)
|
2025-10-03 19:17:01 +03:00
|
|
|
|
|
|
|
|
// external scrapper
|
|
|
|
|
surugayaScrapper := newSurugayaScrapperClient(app.config.GrpcCfg.SurugayaScrapperHost + ":" + app.config.GrpcCfg.SurugayaScrapperPort)
|
2025-10-02 20:35:53 +03:00
|
|
|
|
|
|
|
|
//task processor
|
2025-12-26 18:49:22 +03:00
|
|
|
handlers := make(map[string]parsers.TaskHandler)
|
2025-12-26 16:19:09 +03:00
|
|
|
|
2025-12-26 18:49:22 +03:00
|
|
|
if app.config.OriginEnabled.Surugaya {
|
2026-02-28 10:53:33 +03:00
|
|
|
handlers[shared.OriginSurugaya] = parsers.NewSurugayaParser(surugayaScrapper)
|
2025-12-26 18:49:22 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if app.config.OriginEnabled.Mandarake {
|
2026-02-28 10:53:33 +03:00
|
|
|
handlers[shared.OriginMandarake] = mandarake.NewParser(mandarake.Deps{
|
2025-12-26 16:19:09 +03:00
|
|
|
Enabled: app.config.OriginEnabled.Mandarake,
|
|
|
|
|
ExternalBrowser: app.config.ExternalBrowser,
|
|
|
|
|
GoroutinesNumber: app.numCPUs,
|
2026-02-28 23:33:22 +03:00
|
|
|
TaskTimeout: app.config.TasksCfg.TaskTimeout,
|
2025-12-26 18:49:22 +03:00
|
|
|
})
|
2025-10-02 20:35:53 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
taskProcessor := processor.New(processor.Deps{
|
|
|
|
|
Handlers: handlers,
|
|
|
|
|
Out: sender,
|
2025-10-03 19:17:01 +03:00
|
|
|
State: app.state,
|
2025-10-02 20:35:53 +03:00
|
|
|
Ctx: ctx,
|
2025-10-03 19:17:01 +03:00
|
|
|
NumCPUs: app.numCPUs,
|
2025-10-02 20:35:53 +03:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
process := func() {
|
2025-10-03 19:17:01 +03:00
|
|
|
app.state.SetStatus(appState.StatusRequestTasks)
|
2025-10-02 20:35:53 +03:00
|
|
|
log.Info("Requesting data for parsing")
|
|
|
|
|
|
2025-10-03 19:17:01 +03:00
|
|
|
receivedTasks := app.network.RequestTasks(ctx, apiClient)
|
2025-10-02 20:35:53 +03:00
|
|
|
log.WithField("length", len(receivedTasks)).Debug("End receiving")
|
|
|
|
|
|
2026-02-28 10:53:33 +03:00
|
|
|
taskProcessor.StartWork(ctx, receivedTasks)
|
2025-10-02 20:35:53 +03:00
|
|
|
}
|
|
|
|
|
|
2026-02-28 23:33:22 +03:00
|
|
|
period := time.NewTicker(app.checkPeriod * time.Hour)
|
|
|
|
|
defer period.Stop()
|
|
|
|
|
|
2025-10-02 20:35:53 +03:00
|
|
|
go func() {
|
|
|
|
|
process() //immediate start
|
|
|
|
|
for range period.C {
|
|
|
|
|
process()
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
//done tasks sender
|
|
|
|
|
go func() {
|
|
|
|
|
ticker := time.NewTicker(time.Second * 2)
|
|
|
|
|
defer ticker.Stop()
|
|
|
|
|
|
|
|
|
|
var sendData []shared.TaskResult
|
|
|
|
|
|
|
|
|
|
for {
|
|
|
|
|
select {
|
|
|
|
|
case task := <-sender:
|
|
|
|
|
sendData = append(sendData, task)
|
|
|
|
|
|
|
|
|
|
case <-ticker.C:
|
|
|
|
|
l := len(sendData)
|
|
|
|
|
if l > 0 {
|
|
|
|
|
log.WithField("length", l).Debug("Sending parsed data")
|
2026-03-01 01:01:06 +03:00
|
|
|
app.network.SendResult(ctx, apiClient, sendData)
|
2025-10-02 20:35:53 +03:00
|
|
|
sendData = sendData[:0]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
2026-02-17 12:19:47 +03:00
|
|
|
//start metrics server
|
|
|
|
|
go func() {
|
2026-02-28 10:53:33 +03:00
|
|
|
if err := app.metricsSrv.Run(); err != nil {
|
|
|
|
|
errChan <- err
|
2026-02-17 12:19:47 +03:00
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
2025-10-02 20:35:53 +03:00
|
|
|
//gRPC Server for status response
|
|
|
|
|
go func() {
|
2026-02-28 10:53:33 +03:00
|
|
|
listener, err := net.Listen("tcp", addr)
|
2025-10-02 20:35:53 +03:00
|
|
|
if err != nil {
|
2026-02-28 10:53:33 +03:00
|
|
|
errChan <- err
|
2025-10-02 20:35:53 +03:00
|
|
|
}
|
|
|
|
|
|
2026-02-28 10:53:33 +03:00
|
|
|
log.Infof("gRPC Server listening at %v", addr)
|
|
|
|
|
if err = app.taskApiSrv.Serve(listener); err != nil {
|
|
|
|
|
errChan <- err
|
2025-10-02 20:35:53 +03:00
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
2026-02-28 10:53:33 +03:00
|
|
|
select {
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
app.shutdown(ctx)
|
|
|
|
|
case err := <-errChan:
|
|
|
|
|
log.WithError(err).Fatal("Application run error")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (app *App) shutdown(ctx context.Context) {
|
|
|
|
|
log.Info("Shutting down...")
|
2026-02-17 12:19:47 +03:00
|
|
|
|
2026-02-28 10:53:33 +03:00
|
|
|
app.taskApiSrv.GracefulStop()
|
|
|
|
|
|
|
|
|
|
if err := app.metricsSrv.Shutdown(ctx); err != nil {
|
|
|
|
|
log.WithError(err).Error("Failed to shutdown server")
|
|
|
|
|
}
|
2025-10-02 20:35:53 +03:00
|
|
|
}
|