1st
This commit is contained in:
6452
daemon/Bundesliga.data
Executable file
6452
daemon/Bundesliga.data
Executable file
File diff suppressed because it is too large
Load Diff
1462
daemon/LaLiga.data
Executable file
1462
daemon/LaLiga.data
Executable file
File diff suppressed because it is too large
Load Diff
1452
daemon/Premier League.data
Executable file
1452
daemon/Premier League.data
Executable file
File diff suppressed because it is too large
Load Diff
1477
daemon/Serie A.data
Executable file
1477
daemon/Serie A.data
Executable file
File diff suppressed because it is too large
Load Diff
BIN
daemon/daemon
Executable file
BIN
daemon/daemon
Executable file
Binary file not shown.
273
daemon/daemon.go
Executable file
273
daemon/daemon.go
Executable file
@@ -0,0 +1,273 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gordenko.dev/dima/flashscore"
|
||||
"gordenko.dev/dima/flashscore/model"
|
||||
"gordenko.dev/dima/qx"
|
||||
"gordenko.dev/dima/web"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultCheckInterval = 1 * time.Hour
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
BaseDir string
|
||||
CheckInterval int
|
||||
Logger *log.Logger
|
||||
Db *qx.Db
|
||||
SportIDs []int
|
||||
}
|
||||
|
||||
type Daemon struct {
|
||||
mutex sync.Mutex
|
||||
checkInterval time.Duration
|
||||
fs *flashscore.Flashscore
|
||||
baseDir string
|
||||
dumpFilepath string
|
||||
logger *log.Logger
|
||||
port int
|
||||
model *model.Model
|
||||
// map[sportID]map[matchID]
|
||||
reports map[int]map[string]flashscore.UpcomingMatchReport
|
||||
}
|
||||
|
||||
func New(opt Options) (*Daemon, error) {
|
||||
if opt.Logger == nil {
|
||||
return nil, errors.New("Logger option is required")
|
||||
}
|
||||
if opt.Db == nil {
|
||||
return nil, errors.New("Db option is required")
|
||||
}
|
||||
if opt.BaseDir == "" {
|
||||
return nil, errors.New("BaseDir option is required")
|
||||
}
|
||||
s := &Daemon{
|
||||
logger: opt.Logger,
|
||||
baseDir: opt.BaseDir,
|
||||
dumpFilepath: filepath.Join(opt.BaseDir, "reports.dump"),
|
||||
fs: flashscore.New(opt.Db, opt.Logger),
|
||||
model: model.New(opt.Db),
|
||||
reports: make(map[int]map[string]flashscore.UpcomingMatchReport),
|
||||
}
|
||||
if opt.CheckInterval <= 0 {
|
||||
s.checkInterval = defaultCheckInterval
|
||||
} else {
|
||||
s.checkInterval = time.Duration(opt.CheckInterval) * time.Second
|
||||
}
|
||||
for _, sportID := range opt.SportIDs {
|
||||
s.reports[sportID] = make(map[string]flashscore.UpcomingMatchReport)
|
||||
}
|
||||
err := s.tryRestoreReportsFromDump()
|
||||
if err != nil {
|
||||
// только логгируем, ибо если файла нет или он поврежден - это не проблема
|
||||
s.logger.Printf("tryRestoreReportsFromDump: %s\n", err)
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *Daemon) Close() {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
buf, err := json.MarshalIndent(s.reports, "", " ")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = os.WriteFile(s.dumpFilepath, buf, 0666)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Daemon) tryRestoreReportsFromDump() (err error) {
|
||||
buf, err := os.ReadFile(s.dumpFilepath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return json.Unmarshal(buf, &s.reports)
|
||||
}
|
||||
|
||||
func (s *Daemon) ShowReports(state *web.State, reply *web.Reply) (err error) {
|
||||
buf, _ := json.MarshalIndent(s.reports, "", " ")
|
||||
str := fmt.Sprintf("%s", buf)
|
||||
|
||||
reply.WriteString(str)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Daemon) Run() {
|
||||
ticker := time.NewTicker(s.checkInterval)
|
||||
|
||||
s.logger.Println("tick")
|
||||
for sportID := range s.reports {
|
||||
s.checkUpcomingMatches(sportID)
|
||||
}
|
||||
s.logger.Println("tick processed")
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
s.logger.Println("tick")
|
||||
for sportID := range s.reports {
|
||||
s.checkUpcomingMatches(sportID)
|
||||
}
|
||||
s.logger.Println("tick processed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Daemon) ListUpcomingMatchReports(sportID int) (list []flashscore.UpcomingMatchReport, err error) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
now := time.Now().Unix() - 3600
|
||||
|
||||
reports, ok := s.reports[sportID]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("sportID: %d, reports size: %d\n", sportID, len(reports))
|
||||
|
||||
var started []string
|
||||
|
||||
for matchID, report := range reports {
|
||||
if report.StartTime < now {
|
||||
// Матч уже начался
|
||||
//fmt.Printf("matchStarted: startTime %d < now %d\n", report.StartTime, now)
|
||||
started = append(started, matchID)
|
||||
} else {
|
||||
if report.IsTeamsLoaded && report.IsHistoryLoaded {
|
||||
list = append(list, report)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Удаляем матчи, которые начались
|
||||
for _, matchID := range started {
|
||||
delete(reports, matchID)
|
||||
}
|
||||
|
||||
// Сортируем матчи
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
a := list[i]
|
||||
b := list[j]
|
||||
|
||||
if a.StartTime == b.StartTime {
|
||||
// Время начала равно - сравниваем MatchID
|
||||
return a.MatchID < b.MatchID
|
||||
}
|
||||
|
||||
return a.StartTime < b.StartTime
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Daemon) checkUpcomingMatches(sportID int) {
|
||||
scheduledMatches, err := s.fs.ListOfScheduledMatches(sportID)
|
||||
if err != nil {
|
||||
s.logger.Printf("ListOfScheduledMatches: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Printf("Sport: %d, scheduled matches found: %d\n", sportID, len(scheduledMatches))
|
||||
|
||||
reports := s.reports[sportID]
|
||||
|
||||
for idx, match := range scheduledMatches {
|
||||
if sportID == flashscore.Tennis {
|
||||
// Пары игнорим
|
||||
if flashscore.IsTennisDoublesChamp(match.FullChampName) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
// Здесь лок не нужен, ибо нет race conditions, пишем в мапу в этом же потоке
|
||||
report, ok := reports[match.MatchID]
|
||||
if !ok {
|
||||
report = flashscore.UpcomingMatchReport{
|
||||
MatchID: match.MatchID,
|
||||
SportID: match.SportID,
|
||||
StartTime: match.StartTime.Unix(),
|
||||
Zone: model.TeamZone{
|
||||
ZoneID: match.Zone.ZoneID,
|
||||
Name: match.Zone.Name,
|
||||
SportID: match.SportID,
|
||||
},
|
||||
Champ: model.TeamChamp{
|
||||
ChampID: match.ChampID,
|
||||
},
|
||||
FullChampName: match.FullChampName,
|
||||
}
|
||||
// Ниже по тексту report будет перезаписан в коллекции reports
|
||||
}
|
||||
|
||||
// Даже если отчет полный - синкаем, чтобы обновить odds
|
||||
|
||||
err = s.fs.SyncUpcomingMatchReport(&report)
|
||||
if err != nil {
|
||||
s.logger.Printf("SyncUpcomingMatchReport: %s; matchID=%s\n", err, report.MatchID)
|
||||
} else {
|
||||
report.ParseTime = time.Now()
|
||||
//buf, _ := json.MarshalIndent(report, "", " ")
|
||||
//fmt.Printf("REPORT:\n\n%s\n\n", buf)
|
||||
|
||||
s.mutex.Lock()
|
||||
reports[report.MatchID] = report
|
||||
s.mutex.Unlock()
|
||||
}
|
||||
|
||||
//fmt.Printf("REPORT:\n\n%s\n\n", buf)
|
||||
if (idx % 10) == 0 {
|
||||
s.logger.Printf("%d matches scanned\n", idx+1)
|
||||
}
|
||||
}
|
||||
|
||||
s.logger.Printf("Sport: %d, all matches scanned\n", sportID)
|
||||
}
|
||||
|
||||
/*
|
||||
func (s *Flashscore) ListUpcomingMatchReports(sportID int, reportCh chan *UpcomingMatchReport) (err error) {
|
||||
matches, err := s.listOfScheduledMatches(sportID)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("listOfScheduledMatches: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
//matches = DiscardScheduledMatchesFurtherThan(matches, 24*time.Hour)
|
||||
reports := s.reports
|
||||
|
||||
for _, match := range matches {
|
||||
s.Li
|
||||
|
||||
report = &UpcomingMatchReport{
|
||||
IsComplete: true,
|
||||
MatchID: match.MatchID,
|
||||
SportID: match.SportID,
|
||||
StartTime: match.StartTime.Unix(),
|
||||
}
|
||||
|
||||
report, reportErr := s.getUpcomingMatchReport(match)
|
||||
if reportErr != nil {
|
||||
s.logger.Printf("getUpcomingMatchReport: %s; matchID=%s", reportErr, match.MatchID)
|
||||
} else {
|
||||
reportCh <- &report
|
||||
}
|
||||
}
|
||||
|
||||
close(reportCh)
|
||||
return
|
||||
}
|
||||
*/
|
||||
Reference in New Issue
Block a user