penguin/golifehk

@golifehk bot in Telegram

datasources/mtr/bus/busschedule.go

raw ยท 3036 bytes

package bus

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"path/filepath"
	"time"

	"github.com/tgckpg/golifehk/utils"
)

type Location struct {
	latitude  float32
	longitude float32
}

type Bus struct {
	ETA         int      `json:"arrivalTimeInSecond,string"`
	ETAText     string   `json:"arrivalTimeText"`
	BusId       string   `json:"busId"`
	BusLocation Location `json:"busLocation"`
	ETD         string   `json:"departureTimeInSecond"`
	ETDText     string   `json:"departureTimeText"`
	Delayed     string   `json:"isDelayed"`
	Scheduled   string   `json:"isScheduled"`
	LineRef     string   `json:"lineRef"`
}

type BusStopBuses struct {
	Buses     []Bus  `json:"bus"`
	BusStopId string `json:"busStopId"`
	Suspended string `json:"isSuspended"`
}

type ScheduleStatusTime struct {
	time.Time
}

type BusSchedule struct {
	RefreshTime  int                `json:"appRefreshTimeInSecond,string"`
	BusStops     []BusStopBuses     `json:"busStop"`
	StatusTime   ScheduleStatusTime `json:"routeStatusTime"`
	RemarksTitle string             `json:"routeStatusRemarkTitle"`
	// 0   = OK
	// 100 = Rate limit ( Unconfirmed. Not in spec )
	Status int `json:"status,string"`
}

func (t *ScheduleStatusTime) UnmarshalJSON(b []byte) (err error) {
	date, err := time.Parse(`"2006\/01\/02 15:04"`, string(b))
	if err != nil {
		return err
	}
	t.Time = date
	return
}

func getSchedule(lang string, routeName string) (*BusSchedule, error) {

	CACHE_PATH := filepath.Join(
		utils.WORKDIR,
		"mtr_bsch"+"-"+lang+"-"+routeName+".json",
	)

	postLang := "en"
	if lang == "zh-Hant" {
		postLang = "zh"
	}

	QUERY_FUNC := func() (io.ReadCloser, error) {
		// Query Remote
		values := map[string]string{"language": postLang, "routeName": routeName}
		jsonValue, _ := json.Marshal(values)
		resp, err := http.Post(
			"https://rt.data.gov.hk/v1/transport/mtr/bus/getSchedule",
			"application/json",
			bytes.NewBuffer(jsonValue),
		)

		if err != nil {
			return nil, err
		}

		return resp.Body, nil
	}

	cs, err := utils.CacheStreamEx(CACHE_PATH, QUERY_FUNC)
	if err != nil {
		return nil, err
	}

	oldSch := BusSchedule{
		Status: -1,
	}

	if cs.Local != nil {
		err = json.Unmarshal(cs.Local.Bytes(), &oldSch)
		if err != nil {
			return nil, err
		}
	}

	newSch := BusSchedule{
		Status: -1,
	}

	for i := 0; i < 3; i++ {

		if cs.Remote != nil {
			err = json.Unmarshal(cs.Remote.Bytes(), &newSch)
			if err != nil {
				return nil, err
			}
		}

		if newSch.Status == 0 {
			cs.Commit()
			return &newSch, nil
		}

		if oldSch.Status == 0 {
			if cs.NotExpired(60) || oldSch.StatusTime.Time == newSch.StatusTime.Time {
				log.Printf("Using cache: %s", CACHE_PATH)
				return &oldSch, nil
			}
		}

		// First time + try again i times
		err = cs.Reload()
		log.Printf("Reloading (%d): %s", i, CACHE_PATH)
		if err != nil {
			err = fmt.Errorf("Error retrieving data: %s", err)
			return nil, err
		}
	}

	if newSch.Status != 0 {
		err = fmt.Errorf("%s (%d)", newSch.RemarksTitle, newSch.Status)
	}

	return &newSch, err
}