diff options
| author | Leonardo Bishop <me@leonardobishop.com> | 2025-08-23 22:29:28 +0100 |
|---|---|---|
| committer | Leonardo Bishop <me@leonardobishop.com> | 2025-08-23 22:29:28 +0100 |
| commit | ecc6a55aba7bb35fc778e7a53848396b88214151 (patch) | |
| tree | 1b37a2dc5f4594155114da1ae0c4529d20a4c548 /pkg/schedule | |
| parent | 8f7dec8ba6b2f9bde01afd0a110596ebbd43e0ed (diff) | |
Add multiple conferences feature
Diffstat (limited to 'pkg/schedule')
| -rw-r--r-- | pkg/schedule/model.go | 72 | ||||
| -rw-r--r-- | pkg/schedule/parse.go | 205 | ||||
| -rw-r--r-- | pkg/schedule/service.go | 116 |
3 files changed, 0 insertions, 393 deletions
diff --git a/pkg/schedule/model.go b/pkg/schedule/model.go deleted file mode 100644 index fcf39a5..0000000 --- a/pkg/schedule/model.go +++ /dev/null @@ -1,72 +0,0 @@ -package schedule - -import "time" - -type Schedule struct { - Conference Conference `json:"conference"` - Tracks []Track `json:"tracks"` - Days []Day `json:"days"` -} - -type Conference struct { - Title string `json:"title"` - Venue string `json:"venue"` - City string `json:"city"` - Start string `json:"start"` - End string `json:"end"` - Days int `json:"days"` - DayChange string `json:"dayChange"` - TimeslotDuration string `json:"timeslotDuration"` - BaseURL string `json:"baseUrl"` - TimeZoneName string `json:"timeZoneName"` -} - -type Track struct { - Name string `json:"name"` -} - -type Day struct { - Date string `json:"date"` - Start time.Time `json:"start"` - End time.Time `json:"end"` - Rooms []Room `json:"rooms"` -} - -type Room struct { - Name string `json:"name"` - Events []Event `json:"events"` -} - -type Event struct { - ID int32 `json:"id"` - GUID string `json:"guid"` - Date string `json:"date"` - Start time.Time `json:"start"` - End time.Time `json:"end"` - Duration int32 `json:"duration"` - Room string `json:"room"` - URL string `json:"url"` - Track string `json:"track"` - Type string `json:"type"` - Title string `json:"title"` - Abstract string `json:"abstract"` - Persons []Person `json:"persons"` - Attachments []Attachment `json:"attachments"` - Links []Link `json:"links"` -} - -type Person struct { - ID int `json:"id"` - Name string `json:"name"` -} - -type Attachment struct { - Type string `json:"string"` - Href string `json:"href"` - Name string `json:"name"` -} - -type Link struct { - Href string `json:"href"` - Name string `json:"name"` -} diff --git a/pkg/schedule/parse.go b/pkg/schedule/parse.go deleted file mode 100644 index b0ed8df..0000000 --- a/pkg/schedule/parse.go +++ /dev/null @@ -1,205 +0,0 @@ -package schedule - -import ( - "encoding/xml" - "fmt" - "time" -) - -type schedule struct { - XMLName xml.Name `xml:"schedule"` - Conference conference `xml:"conference"` - Tracks []track `xml:"tracks>track"` - Days []day `xml:"day"` -} - -type conference struct { - Title string `xml:"title"` - Venue string `xml:"venue"` - City string `xml:"city"` - Start string `xml:"start"` - End string `xml:"end"` - Days int `xml:"days"` - DayChange string `xml:"day_change"` - TimeslotDuration string `xml:"timeslot_duration"` - BaseURL string `xml:"base_url"` - TimeZoneName string `xml:"time_zone_name"` -} - -type track struct { - Name string `xml:",chardata"` -} - -type day struct { - Date string `xml:"date,attr"` - Start string `xml:"start,attr"` - End string `xml:"end,attr"` - Rooms []room `xml:"room"` -} - -type room struct { - Name string `xml:"name,attr"` - Events []event `xml:"event"` -} - -type event struct { - ID int32 `xml:"id,attr"` - GUID string `xml:"guid,attr"` - Date string `xml:"date"` - Start string `xml:"start"` - Duration string `xml:"duration"` - Room string `xml:"room"` - URL string `xml:"url"` - Track string `xml:"track"` - Type string `xml:"type"` - Title string `xml:"title"` - Abstract string `xml:"abstract"` - Persons []person `xml:"persons>person"` - Attachments []attachment `xml:"attachments>attachment"` - Links []link `xml:"links>link"` -} - -type person struct { - ID int `xml:"id,attr"` - Name string `xml:",chardata"` -} - -type attachment struct { - Type string `xml:"id,attr"` - Href string `xml:"href,attr"` - Name string `xml:",chardata"` -} - -type link struct { - Href string `xml:"href,attr"` - Name string `xml:",chardata"` -} - -func (dst *Schedule) Scan(src schedule) error { - dst.Conference.Scan(src.Conference) - - dst.Tracks = make([]Track, len(src.Tracks)) - for i := range src.Tracks { - dst.Tracks[i].Scan(src.Tracks[i]) - } - dst.Days = make([]Day, len(src.Days)) - for i := range src.Days { - if err := dst.Days[i].Scan(src.Days[i]); err != nil { - return fmt.Errorf("failed to scan day: %w", err) - } - } - return nil -} - -func (dst *Conference) Scan(src conference) { - dst.Title = src.Title - dst.Venue = src.Venue - dst.City = src.City - dst.Start = src.Start - dst.End = src.End - dst.Days = src.Days - dst.DayChange = src.DayChange - dst.TimeslotDuration = src.TimeslotDuration - dst.BaseURL = src.BaseURL - dst.TimeZoneName = src.TimeZoneName -} - -func (dst *Track) Scan(src track) { - dst.Name = src.Name -} - -func (dst *Day) Scan(src day) error { - dst.Date = src.Date - - start, err := time.Parse(time.RFC3339, src.Start) - if err != nil { - return fmt.Errorf("failed to parse start time: %w", err) - } - end, err := time.Parse(time.RFC3339, src.End) - if err != nil { - return fmt.Errorf("failed to parse end time: %w", err) - } - - dst.Start = start - dst.End = end - - dst.Rooms = make([]Room, len(src.Rooms)) - for i := range src.Rooms { - dst.Rooms[i].Scan(src.Rooms[i]) - } - return nil -} - -func (dst *Room) Scan(src room) { - dst.Name = src.Name - - dst.Events = make([]Event, len(src.Events)) - for i := range src.Events { - dst.Events[i].Scan(src.Events[i]) - } -} - -func (dst *Event) Scan(src event) error { - dst.ID = src.ID - dst.GUID = src.GUID - dst.Date = src.Date - - duration, err := parseDuration(src.Duration) - if err != nil { - return err - } - start, err := time.Parse(time.RFC3339, src.Date) - if err != nil { - start = time.Unix(0, 0) - } - dst.Start = start - dst.End = start.Add(time.Minute * time.Duration(duration)) - - dst.Room = src.Room - dst.URL = src.URL - dst.Track = src.Track - dst.Type = src.Type - dst.Title = src.Title - dst.Abstract = src.Abstract - - dst.Persons = make([]Person, len(src.Persons)) - for i := range src.Persons { - dst.Persons[i].Scan(src.Persons[i]) - } - - dst.Attachments = make([]Attachment, len(src.Attachments)) - for i := range src.Attachments { - dst.Attachments[i].Scan(src.Attachments[i]) - } - - dst.Links = make([]Link, len(src.Links)) - for i := range src.Links { - dst.Links[i].Scan(src.Links[i]) - } - - return nil -} - -func (dst *Person) Scan(src person) { - dst.ID = src.ID - dst.Name = src.Name -} - -func (dst *Attachment) Scan(src attachment) { - dst.Type = src.Type - dst.Href = src.Href - dst.Name = src.Name -} - -func (dst *Link) Scan(src link) { - dst.Href = src.Href - dst.Name = src.Name -} - -func parseDuration(duration string) (int32, error) { - d, err := time.Parse("15:04", duration) - if err != nil { - return 0, err - } - return int32(d.Minute() + d.Hour()*60), nil -} diff --git a/pkg/schedule/service.go b/pkg/schedule/service.go deleted file mode 100644 index a73a469..0000000 --- a/pkg/schedule/service.go +++ /dev/null @@ -1,116 +0,0 @@ -package schedule - -import ( - "bufio" - "encoding/xml" - "fmt" - "net/http" - "sync" - "time" -) - -type Service interface { - GetSchedule() (*Schedule, time.Time, error) - GetEventByID(id int32) *Event -} - -type service struct { - pentabarfUrl string - - schedule *Schedule - eventsById map[int32]Event - lastUpdated time.Time - accessLock sync.RWMutex - updateLock sync.Mutex -} - -// TODO: Create a service implementation that persists to DB -// and isn't in memory -func NewService(pentabarfUrl string) (Service, error) { - service := &service{ - pentabarfUrl: pentabarfUrl, - lastUpdated: time.Unix(0, 0), - } - - err := service.updateSchedule() - if err != nil { - return nil, fmt.Errorf("could not read schedule from '%s' (is it a valid pentabarf XML file?): %w", pentabarfUrl, err) - } - return service, nil -} - -func (s *service) GetSchedule() (*Schedule, time.Time, error) { - err := s.updateSchedule() - if err != nil { - return nil, time.Time{}, err - } - - s.accessLock.RLock() - defer s.accessLock.RUnlock() - - return s.schedule, s.lastUpdated, nil -} - -func (s *service) GetEventByID(id int32) *Event { - s.accessLock.RLock() - defer s.accessLock.RUnlock() - - event := s.eventsById[id] - - return &event -} - -func (s *service) hasScheduleExpired() bool { - expire := s.lastUpdated.Add(15 * time.Minute) - return time.Now().After(expire) -} - -func (s *service) updateSchedule() error { - if !s.hasScheduleExpired() { - return nil - } - - if !s.updateLock.TryLock() { - // don't block if another goroutine is already fetching - return nil - } - defer s.updateLock.Unlock() - - res, err := http.Get(s.pentabarfUrl) - if err != nil { - return err - } - - reader := bufio.NewReader(res.Body) - - var schedule schedule - - decoder := xml.NewDecoder(reader) - if err := decoder.Decode(&schedule); err != nil { - return fmt.Errorf("failed to decode XML: %w", err) - } - - var newSchedule Schedule - err = newSchedule.Scan(schedule) - if err != nil { - return fmt.Errorf("failed to scan schedule: %w", err) - } - - s.accessLock.Lock() - defer s.accessLock.Unlock() - - s.schedule = &newSchedule - s.lastUpdated = time.Now() - - s.eventsById = make(map[int32]Event) - - for _, day := range newSchedule.Days { - for _, room := range day.Rooms { - for _, event := range room.Events { - s.eventsById[event.ID] = event - } - } - } - - return nil -} |
