Generalize cache get with expire param

This commit is contained in:
2022-09-14 17:12:48 +08:00
parent 7e327abbae
commit afb73b70ff
10 changed files with 132 additions and 112 deletions

View File

@@ -0,0 +1,76 @@
package bus
import (
"bytes"
"encoding/json"
"io"
"net/http"
"path/filepath"
"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 BusStopSchedules struct {
Buses [] Bus `json:"bus"`
Suspended string `json:"isSuspended"`
}
type BusSchedule struct {
RefreshTime int `json:"appRefreshTimeInSecond,string"`
BusStops [] BusStopSchedules `json:"busStop"`
}
func getSchedule( lang string, routeName string ) ( *BusSchedule, error ) {
CACHE_PATH := filepath.Join(
utils.WORKDIR,
"mtr_bsch" + "-" + lang + "-" + routeName + ".json",
)
QUERY_FUNC := func() ( io.ReadCloser, error ) {
// Query Remote
values := map[string]string { "language": lang , "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
}
buff, err := utils.CacheStream( CACHE_PATH, QUERY_FUNC, 60 )
if err != nil {
return nil, err
}
schedules := BusSchedule{}
err = json.Unmarshal( buff.Bytes(), &schedules )
if err != nil {
return nil, err
}
return &schedules, nil
}

View File

@@ -0,0 +1,12 @@
package bus
import (
"testing"
)
func TestGetSchedule(t *testing.T) {
_, err := getSchedule( "en", "K66" )
if err != nil {
t.Error( err )
}
}

View File

@@ -0,0 +1,98 @@
package bus
import (
"encoding/csv"
"fmt"
"io"
"net/http"
"path/filepath"
"strconv"
"strings"
"github.com/tgckpg/golifehk/utils"
)
type BusStop struct {
RouteId string
Direction string
StationSeq string
StationId string
Latitude float64
Longtitude float64
Name_zhant string
Name_en string
}
var mBusStops *map[string]BusStop
var CSV_BUSSTOPS string = filepath.Join( utils.WORKDIR, "mtr_bus_stops.csv" )
func readBusStopData( r io.Reader ) ( *map[string]BusStop, error ) {
reader := csv.NewReader( r )
entries, err := reader.ReadAll()
if err != nil {
return nil, err
}
busStops := map[string]BusStop{}
var headers []string
for i, line := range entries {
if i == 0 {
headers = line
line[0] = strings.TrimLeft( line[0], utils.BOM )
continue
}
var entry BusStop
for j, value := range line {
switch headers[j] {
case "ROUTE_ID":
entry.RouteId = value
case "DIRECTION":
entry.Direction = value
case "STATION_SEQNO":
entry.StationSeq = value
case "STATION_ID":
entry.StationId = value
case "STATION_LATITUDE":
v, _ := strconv.ParseFloat( value, 64 )
entry.Latitude = v
case "STATION_LONGITUDE":
v, _ := strconv.ParseFloat( value, 64 )
entry.Longtitude = v
case "STATION_NAME_CHI":
entry.Name_zhant = value
case "STATION_NAME_ENG":
entry.Name_en = value
default:
return nil, fmt.Errorf( "Unknown header \"%s\"", headers[j] )
}
}
if _, t := busStops[ entry.StationId ]; t {
return nil, fmt.Errorf( "Duplicated entry %+v", entry )
}
busStops[ entry.StationId ] = entry
}
return &busStops, nil
}
func getBusStops() (*map[string]BusStop, error) {
QUERY_FUNC := func() ( io.ReadCloser, error ) {
resp, err := http.Get( "https://opendata.mtr.com.hk/data/mtr_bus_stops.csv" )
if err != nil {
return nil, err
}
return resp.Body, nil
}
buff, err := utils.CacheStream( CSV_BUSSTOPS, QUERY_FUNC, 7 * 24 * 3600 )
if err != nil {
return nil, err
}
return readBusStopData( buff )
}

View File

@@ -0,0 +1,8 @@
package bus
import (
"testing"
)
func TestAll(t *testing.T) {
}

View File

@@ -0,0 +1,103 @@
package bus
import (
"fmt"
"strings"
"github.com/tgckpg/golifehk/utils"
)
type QueryObject struct {
Route string
BusStops *[]BusStop
}
type QueryResult struct {
BusStops []BusStop
err string
}
type Term struct {
Value string
ProblyRoute bool
}
func Query( message string ) *QueryResult {
return &QueryResult{ err: "No Result" }
}
func test( entry BusStop, val string ) bool {
switch true {
case strings.Contains( entry.Name_zhant, val ):
fallthrough
case strings.Contains( entry.Name_en, val ):
return true
}
return false
}
func parse( line string ) ( *QueryObject, error ) {
busStops, err := getBusStops()
if err != nil {
return nil, err
}
var route string = ""
var searches = []string{}
matches := []BusStop{}
// Sanitize and assume properties for each of the keywords
terms := []Term{}
for _, val := range strings.Split( line, " " ) {
val = strings.ToUpper( strings.Trim( val, " " ) )
term := Term{
Value: val,
ProblyRoute: strings.ContainsAny( val, utils.ROUTE_CHARS ),
}
terms = append( terms, term )
}
// Search for route name first, otherwise search in other props
for _, entry := range *busStops {
// Search for RouteId
for _, term := range terms {
if term.ProblyRoute && term.Value == entry.RouteId {
if route != "" && route != term.Value {
return nil, fmt.Errorf( "Cannot %s & %s", route, term.Value )
}
matches = append( matches, entry )
route = entry.RouteId
break
}
searches = append( searches, term.Value )
if test( entry, term.Value ) {
matches = append( matches, entry )
break
}
}
}
// If route found, filter out all other route
// then search within that route
if route != "" && 0 < len( searches ) {
matches_in := []BusStop{}
for _, entry := range matches {
if entry.RouteId != route {
continue
}
for _, val := range searches {
if test( entry, val ) {
matches_in = append( matches_in, entry )
break
}
}
}
matches = matches_in
}
return &QueryObject{ Route: route, BusStops: &matches }, err
}

View File

@@ -0,0 +1,16 @@
package bus
import (
"fmt"
"testing"
)
func TestQuerySchedule(t *testing.T) {
qo, err := parse( "K74 天瑞" )
if err != nil {
t.Error( err )
}
fmt.Printf( "%+v", qo )
}