2 Commits 2ff169dc1d ... 7c33806156

Author SHA1 Message Date
  Adam 7c33806156 show full or hybrid timetable depending on URL query 3 months ago
  Adam a10552e0d2 initialise runtime environment 3 months ago
9 changed files with 282 additions and 81 deletions
  1. 45 0
      config/config.go
  2. 30 2
      file/file.go
  3. 35 18
      main.go
  4. 97 1
      server/router.go
  5. 24 46
      traffic/access.go
  6. 5 13
      traffic/convert.go
  7. 2 1
      traffic/feeds/feeds.go
  8. 4 0
      traffic/feeds/poznan_ztm.go
  9. 40 0
      traffic/structs/access.go

+ 45 - 0
config/config.go

@@ -0,0 +1,45 @@
+package config
+
+import (
+	"notabug.org/apiote/bimba_server/traffic/feeds"
+
+	"bufio"
+	"os"
+	"strings"
+)
+
+type Config struct {
+	FeedsPath    string
+	EnabledFeeds map[string]feeds.Feed
+}
+
+func Read(filePath string) Config {
+	configFile, _ := os.Open(filePath)
+	defer configFile.Close()
+	scanner := bufio.NewScanner(configFile)
+	config := Config{
+		EnabledFeeds: map[string]feeds.Feed{},
+	}
+	for scanner.Scan() {
+		line := scanner.Text()
+		if line == "" || line[0] == '#' {
+			continue
+		}
+		assignment := strings.Split(line, "=")
+		key := strings.Trim(assignment[0], " ")
+		value := strings.Trim(assignment[1], " ")
+		if key == "feeds_path" {
+			config.FeedsPath = strings.ReplaceAll(value, "\"", "")
+		}
+		if key == "enabled_feeds" {
+			feedsMap := feeds.RegisterFeeds()
+			value = strings.Replace(strings.Replace(value, "[", "", 1), "]", "", 1)
+			elements := strings.Split(value, ",")
+			for _, e := range elements {
+				e = strings.ReplaceAll(strings.Trim(e, " "), "\"", "")
+				config.EnabledFeeds[e] = feedsMap[e]
+			}
+		}
+	}
+	return config
+}

+ 30 - 2
file/file.go

@@ -1,6 +1,7 @@
 package file
 
 import (
+	traffic_structs "notabug.org/apiote/bimba_server/traffic/structs"
 	"notabug.org/apiote/bimba_server/traffic/feeds"
 
 	"archive/tar"
@@ -10,9 +11,9 @@ import (
 	"log"
 	"os"
 	"path/filepath"
+	"sort"
 	"strings"
 	"time"
-	"sort"
 
 	"github.com/ulikunitz/xz"
 )
@@ -232,7 +233,7 @@ func CleanOldVersions(dataHome, feedName string, location *time.Location) {
 		versionString := strings.Replace(name, ".txz", "", 1)
 		// todo func feeds.MakeVersion(string) Version
 		versionDates := strings.Split(versionString, "_")
-		validFrom, _:= time.Parse("20060102", versionDates[0])
+		validFrom, _ := time.Parse("20060102", versionDates[0])
 		validTill, _ := time.Parse("20060102", versionDates[1])
 		version := feeds.Version{
 			ValidFrom: validFrom,
@@ -275,3 +276,30 @@ func CleanOldVersions(dataHome, feedName string, location *time.Location) {
 		}
 	}
 }
+
+func ReadIndexes(dataHome string, feedName string, codeIndex *map[string]map[string]map[string]uint, versions []feeds.Version) {
+	ix := *codeIndex
+	for _, v := range versions {
+		versionCode := v.ValidFrom.Format("20060102") + "_" + v.ValidTill.Format("20060102")
+		index := traffic_structs.ReadCodeIndex(dataHome, feedName, versionCode)
+		if ix[feedName] == nil {
+			ix[feedName] = map[string]map[string]uint{}
+		}
+		ix[feedName][versionCode] = index
+	}
+	*codeIndex = ix
+}
+
+func ReadCalendar(dataHome, feedName string, schedules *map[string]map[string][]traffic_structs.Schedule, versions []feeds.Version) {
+	s := *schedules
+	for _, v := range versions {
+		versionCode := v.ValidFrom.Format("20060102") + "_" + v.ValidTill.Format("20060102")
+		schedule := traffic_structs.ReadCalendar(dataHome, feedName, versionCode)
+		if s[feedName] == nil {
+			s[feedName] = map[string][]traffic_structs.Schedule{}
+		}
+		s[feedName][versionCode] = schedule
+	}
+	*schedules = s
+
+}

+ 35 - 18
main.go

@@ -1,25 +1,41 @@
 package main
 
 import (
+	"notabug.org/apiote/bimba_server/config"
 	"notabug.org/apiote/bimba_server/file"
 	"notabug.org/apiote/bimba_server/server"
 	"notabug.org/apiote/bimba_server/traffic"
+	"notabug.org/apiote/bimba_server/traffic/feeds"
+	traffic_structs "notabug.org/apiote/bimba_server/traffic/structs"
 
+	"context"
 	"fmt"
 	"os"
 	"os/signal"
 	"syscall"
+
+	"path/filepath"
 )
 
-func initialiseTraffic(sigChan chan os.Signal) {
+func initialiseTraffic(sigChan chan os.Signal, doneChan chan bool, cfg config.Config, codeIndex *map[string]map[string]map[string]uint, calendar *map[string]map[string][]traffic_structs.Schedule, versions *map[string][]feeds.Version) {
 	for {
 		sig := <-sigChan
-		if sig == syscall.SIGTERM {
+		if sig == os.Interrupt {
 			break
 		}
-		file.UnpackTraffic("/usr/share/bimba/", "poznan_ztm")
-		file.CleanOldVersions("/usr/share/bimba/", "poznan_ztm", feed.GetLocation())
+		allVersions := map[string][]feeds.Version{}
+		for _, feed := range cfg.EnabledFeeds {
+			file.UnpackTraffic(cfg.FeedsPath, feed.String())
+			file.CleanOldVersions(cfg.FeedsPath, feed.String(), feed.GetLocation())
+			feedHome := filepath.Join(cfg.FeedsPath, feed.String())
+			feedVersions := file.ListVersions(feedHome, feed.GetLocation())
+			allVersions[feed.String()] = feedVersions
+			file.ReadIndexes(cfg.FeedsPath, feed.String(), codeIndex, feedVersions)
+			file.ReadCalendar(cfg.FeedsPath, feed.String(), calendar, feedVersions)
+		}
+		*versions = allVersions
 	}
+	doneChan <- true
 }
 
 func main() {
@@ -27,29 +43,30 @@ func main() {
 		fmt.Println("bimba_server [-c <config>] <command> ")
 		return
 	}
-	configFile := "/etc/bimba.toml"
+	configFilePath := "/etc/bimba.toml"
 	command := os.Args[1]
 	if os.Args[1] == "-c" {
-		configFile = os.Args[2]
+		configFilePath = os.Args[2]
 		command = os.Args[3]
 	}
-	fmt.Println(configFile)
+	cfg := config.Read(configFilePath)
 	switch command {
 	case "convert":
-		traffic.Prepare([]string{"poznan_ztm"}, -1)
+		traffic.Prepare(cfg, -1)
 	case "serve":
 		c := make(chan os.Signal, 1)
-		signal.Notify(c, syscall.SIGTERM, syscall.SIGUSR1)
-		go initialiseTraffic(c)
+		d := make(chan bool)
+		signal.Notify(c, os.Interrupt, syscall.SIGUSR1)
+		codeIndexes := &map[string]map[string]map[string]uint{}
+		versions := &map[string][]feeds.Version{}
+		calendar := &map[string]map[string][]traffic_structs.Schedule{}
+		go initialiseTraffic(c, d, cfg, codeIndexes, calendar, versions)
 		c <- syscall.SIGUSR1
-		server.Route()
-	case "departures":
-		code := os.Args[2]
-		ix := traffic.ReadCodeIndex("/usr/share/bimba/", "poznan_ztm", "now")
-		calendar := traffic.ReadCalendar("/usr/share/bimba/", "poznan_ztm", "now")
-		departures := traffic.GetDeparturesNow(code, "/usr/share/bimba/", "poznan_ztm", ix, calendar)
-		for _, departure := range departures {
-			fmt.Printf("%s -> %s @ %02d:%02d:%02d (%s) RT:%v Status:%v {%s}\n", departure.Line, departure.Headsign, departure.Time.Hour, departure.Time.Minute, departure.Time.Second, departure.Time.Zone, departure.IsRealtime, departure.Status, departure.StopOrder)
+		srv := server.Route(cfg, codeIndexes, calendar, versions)
+		<-d
+		if err := srv.Shutdown(context.Background()); err != nil {
+			panic(err)
 		}
+		fmt.Println("Nothing Arrived")
 	}
 }

+ 97 - 1
server/router.go

@@ -1,5 +1,101 @@
 package server
 
-func Route() {
+import (
+	"notabug.org/apiote/bimba_server/config"
+	"notabug.org/apiote/bimba_server/traffic"
+	"notabug.org/apiote/bimba_server/traffic/feeds"
+	traffic_structs "notabug.org/apiote/bimba_server/traffic/structs"
 
+	"fmt"
+	"io"
+	"log"
+	"net/http"
+	"strings"
+	"time"
+)
+
+func handleRoot(w http.ResponseWriter, r *http.Request) {
+	// todo send enabled feeds
+}
+
+func handleFeed(w http.ResponseWriter, r *http.Request, feedName string) {
+	// todo send feed
+}
+
+func handleStops(w http.ResponseWriter, r *http.Request, feedName string) {
+	// todo ?q=query
+	// todo query in nameIndex
+}
+
+func handleDepartures(w http.ResponseWriter, r *http.Request, feedName string, cfg config.Config, versions []feeds.Version, codeIndex map[string]map[string]uint, calendar map[string][]traffic_structs.Schedule) {
+	r.ParseForm()
+	code := r.Form.Get("code")
+	date := r.Form.Get("date")
+	// todo line := r.Form.Get("line")
+	versionCode := ""
+	departuresType := "full"
+	if date == "" {
+		feedNow := time.Now().In(cfg.EnabledFeeds[feedName].GetLocation())
+		date = feedNow.Format("20060102")
+		departuresType = "hybrid"
+	}
+	feedTime, _ := time.ParseInLocation("20060102", date, cfg.EnabledFeeds[feedName].GetLocation())
+	for _, v := range versions {
+		if !v.ValidFrom.After(feedTime) && !feedTime.After(v.ValidTill) {
+			versionCode = v.ValidFrom.Format("20060102") + "_" + v.ValidTill.Format("20060102")
+		}
+	}
+
+	// todo if versionCode == "" -> error
+
+	ix := codeIndex[versionCode]
+	cal := calendar[versionCode]
+
+	departures := traffic.GetDepartures(code, cfg.FeedsPath, feedName, versionCode, ix, cal, date, departuresType)
+
+	// this is only temporary
+	now := time.Now()
+	for _, departure := range departures {
+		departureTime := time.Date(now.Year(), now.Month(), now.Day(), int(departure.Time.Hour), int(departure.Time.Minute), int(departure.Time.Second), 0, now.Location())
+		dayOffset, _ := time.ParseDuration(fmt.Sprintf("%dh", departure.Time.DayOffset*24))
+		departureTime = departureTime.Add(dayOffset)
+		timeTo := int(departureTime.Sub(now).Minutes())
+		line := fmt.Sprintf("%s -> %s @ %02d:%02d:%02d (%d) RT:%v Status:%v\n", departure.Line, departure.Headsign, departure.Time.Hour, departure.Time.Minute, departure.Time.Second, timeTo, departure.IsRealtime, departure.Status)
+		io.WriteString(w, line)
+	}
+
+}
+
+func Route(cfg config.Config, codeIndexes *map[string]map[string]map[string]uint, calendar *map[string]map[string][]traffic_structs.Schedule, versions *map[string][]feeds.Version) *http.Server {
+	srv := &http.Server{Addr: ":51354"}
+
+	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+		if r.URL.Path[1:] == "" {
+			handleRoot(w, r)
+		} else {
+			path := strings.Split(r.URL.Path[1:], "/")
+			feedName := path[0]
+			if len(path) == 1 {
+				handleFeed(w, r, feedName)
+			} else {
+				v := *versions
+				ix := *codeIndexes
+				c := *calendar
+				resource := path[1]
+				switch resource {
+				case "stops":
+					handleStops(w, r, feedName)
+				case "departures":
+					handleDepartures(w, r, feedName, cfg, v[feedName], ix[feedName], c[feedName])
+				}
+			}
+		}
+	})
+
+	go func() {
+		if err := srv.ListenAndServe(); err != http.ErrServerClosed {
+			log.Fatalf("ListenAndServe(): %v", err)
+		}
+	}()
+	return srv
 }

+ 24 - 46
traffic/access.go

@@ -72,18 +72,22 @@ func marshalStopOrder(tripOffset uint, stopOrder int) string {
 	return hex.EncodeToString(bytes)
 }
 
-func GetDeparturesNow(stopCode, dataHome, feedName string, ix map[string]uint, calendar []trafficStructs.Schedule) []apiStructs.Departure {
-	timetableHome := filepath.Join(dataHome, feedName, "now")
-	stopOffset := ix[stopCode]
+func GetDepartures(stopCode, dataHome, feedName, versionCode string, codeIndex map[string]uint, calendar []trafficStructs.Schedule, date string, departuresType string) []apiStructs.Departure {
+	stopOffset := codeIndex[stopCode]
 
 	deadzone, _ := time.ParseDuration("-1m")
 	location, _ := time.LoadLocation("Europe/Warsaw") // todo stopLocation ?: feedLocation
+	thisDay, _ := time.Parse("20060102", date)
 	now := time.Now()
-	minuteB4Now := now.Add(deadzone)
-	midnight := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
-	today := now.Format("2006-01-02")
-	yesterdayTime := now.AddDate(0, 0, -1)
+	thisTime := time.Date(thisDay.Year(), thisDay.Month(), thisDay.Day(), now.Hour(), now.Minute(), now.Second(), 0, now.Location())
+	minuteB4Now := thisTime.Add(deadzone)
+	midnight := time.Date(thisTime.Year(), thisTime.Month(), thisTime.Day(), 0, 0, 0, 0, thisTime.Location())
+	today := thisTime.Format("2006-01-02")
+	yesterdayTime := thisTime.AddDate(0, 0, -1)
 	yesterday := yesterdayTime.Format("2006-01-02")
+
+	timetableHome := filepath.Join(dataHome, feedName, versionCode)
+
 	todaySchedule := findSchedule(timetableHome, today, calendar)
 	yesterdaySchedule := findSchedule(timetableHome, yesterday, calendar)
 
@@ -133,11 +137,14 @@ func GetDeparturesNow(stopCode, dataHome, feedName string, ix map[string]uint, c
 			bare.UnmarshalReader(departuresFile, &departureRaw)
 		}
 		// todo if tripID != -> error
-		update := getRealtimeOffset(tripID, order.Order)
+		update := gtfs_rt.Update{}
+		if departuresType == "hybrid" {
+			update = getRealtimeOffset(tripID, order.Order)
+		}
 		departureTime := calculateGtfsTime(departureRaw.Time, update.Delay, today, location)
-		if departureTime.After(minuteB4Now) {
+		if departuresType == "full" || departureTime.After(minuteB4Now) {
 			status := apiStructs.VEHICLE_IN_TRANSIT
-			timeToArrival := departureTime.Sub(now).Minutes()
+			timeToArrival := departureTime.Sub(thisTime).Minutes()
 			if apiStructs.VehicleStatus(update.Status) == apiStructs.VEHICLE_AT_STOP {
 				status = apiStructs.VEHICLE_AT_STOP
 			} else if timeToArrival < 0 {
@@ -174,12 +181,15 @@ func GetDeparturesNow(stopCode, dataHome, feedName string, ix map[string]uint, c
 		}
 		// todo if tripID != -> error
 		departureTime := calculateGtfsTime(departureRaw.Time, 0, yesterday, location)
-		if departureTime.After(midnight) {
-			update := getRealtimeOffset(tripID, order.Order)
+		if departuresType == "full" || departureTime.After(midnight) {
+			update := gtfs_rt.Update{}
+			if departuresType == "hybrid" {
+				update = getRealtimeOffset(tripID, order.Order)
+			}
 			departureTime := calculateGtfsTime(departureRaw.Time, update.Delay, yesterday, location)
-			if departureTime.After(minuteB4Now) {
+			if (departuresType == "full" && departureTime.After(midnight)) || (departuresType == "hybrid" && departureTime.After(minuteB4Now)) {
 				status := apiStructs.VEHICLE_IN_TRANSIT
-				timeToArrival := departureTime.Sub(now).Minutes()
+				timeToArrival := departureTime.Sub(thisTime).Minutes()
 				if apiStructs.VehicleStatus(update.Status) == apiStructs.VEHICLE_AT_STOP {
 					status = apiStructs.VEHICLE_AT_STOP
 				} else if timeToArrival < 0 {
@@ -220,35 +230,3 @@ func GetDeparturesNow(stopCode, dataHome, feedName string, ix map[string]uint, c
 	})
 	return departures
 }
-
-func ReadCalendar(dataHome, feedName, date string) []trafficStructs.Schedule {
-	timetableHome := filepath.Join(dataHome, feedName, date)
-	calendarFile, _ := os.Open(filepath.Join(timetableHome, "calendar.bare"))
-	calendar := []trafficStructs.Schedule{}
-	var err error = nil
-	for err == nil {
-		schedule := trafficStructs.Schedule{}
-		err = bare.UnmarshalReader(calendarFile, &schedule)
-		if err == nil {
-			calendar = append(calendar, schedule)
-		}
-	}
-	return calendar
-}
-
-func ReadCodeIndex(dataHome, feedName string, date string) map[string]uint {
-	timetableHome := filepath.Join(dataHome, feedName, date)
-	ixFile, _ := os.Open(filepath.Join(timetableHome, "ix_stop_codes.bare"))
-	defer ixFile.Close()
-
-	ix := map[string]uint{}
-	r := bare.NewReader(ixFile)
-	num, _ := r.ReadUint()
-	for i := uint64(0); i < num; i++ {
-		k, _ := r.ReadString()
-		v, _ := r.ReadUint()
-		ix[k] = uint(v)
-	}
-
-	return ix
-}

+ 5 - 13
traffic/convert.go

@@ -2,6 +2,7 @@ package traffic
 
 import (
 	"notabug.org/apiote/bimba_server/file"
+	"notabug.org/apiote/bimba_server/config"
 	"notabug.org/apiote/bimba_server/traffic/feeds"
 	"notabug.org/apiote/bimba_server/traffic/structs"
 
@@ -13,7 +14,6 @@ import (
 	"net/http"
 	"os"
 	"path/filepath"
-	"runtime"
 	"sort"
 	"strings"
 	"syscall"
@@ -521,29 +521,21 @@ func createStopCodeIndex(path string, stopsOffsetsByCode map[string]uint) {
 	result.Write(bytes)
 }
 
-func Prepare(feedNames []string, bimbaPid int) {
-	feeds := feeds.RegisterFeeds()
+func Prepare(cfg config.Config, bimbaPid int) {
 	tmp := os.TempDir()
 
-	dataHome := ""
-	if runtime.GOOS == "linux" {
-		dataHome = "/usr/share/bimba/"
-	} else {
-		log.Fatalf("%s not supported\n", runtime.GOOS)
-	}
-
-	for _, feedName := range feedNames {
+	for _, feed := range cfg.EnabledFeeds {
+		feedName := feed.String()
 		p := filepath.Join(tmp, feedName)
 		err := os.MkdirAll(p, 0755)
 		if err != nil {
 			log.Fatalf("Cannot mkdir %s", p)
 		}
-		feedHome := filepath.Join(dataHome, feedName)
+		feedHome := filepath.Join(cfg.FeedsPath, feedName)
 		err = os.MkdirAll(feedHome, 0755)
 		if err != nil {
 			log.Fatalf("Cannot mkdir %s", feedHome)
 		}
-		feed := feeds[feedName]
 
 		location := feed.GetLocation()
 		downloadedVersions := file.ListVersions(feedHome, location)

+ 2 - 1
traffic/feeds/feeds.go

@@ -7,7 +7,8 @@ import (
 type Feed interface {
 	ConvertVehicles(string) error
 	GetVersions(time.Time) ([]Version, error)
- 	GetLocation() *time.Location
+	GetLocation() *time.Location
+	String() string
 }
 
 type Version struct {

+ 4 - 0
traffic/feeds/poznan_ztm.go

@@ -170,3 +170,7 @@ func (z ZtmPoznan) GetLocation() *time.Location {
 	l, _ := time.LoadLocation("Europe/Warsaw")
 	return l
 }
+
+func (z ZtmPoznan) String() string {
+	return "poznan_ztm"
+}

+ 40 - 0
traffic/structs/access.go

@@ -0,0 +1,40 @@
+package structs
+
+import (
+	"os"
+	"path/filepath"
+
+	"git.sr.ht/~sircmpwn/go-bare"
+)
+
+func ReadCalendar(dataHome, feedName, date string) []Schedule {
+	timetableHome := filepath.Join(dataHome, feedName, date)
+	calendarFile, _ := os.Open(filepath.Join(timetableHome, "calendar.bare"))
+	calendar := []Schedule{}
+	var err error = nil
+	for err == nil {
+		schedule := Schedule{}
+		err = bare.UnmarshalReader(calendarFile, &schedule)
+		if err == nil {
+			calendar = append(calendar, schedule)
+		}
+	}
+	return calendar
+}
+
+func ReadCodeIndex(dataHome, feedName string, date string) map[string]uint {
+	timetableHome := filepath.Join(dataHome, feedName, date)
+	ixFile, _ := os.Open(filepath.Join(timetableHome, "ix_stop_codes.bare"))
+	defer ixFile.Close()
+
+	ix := map[string]uint{}
+	r := bare.NewReader(ixFile)
+	num, _ := r.ReadUint()
+	for i := uint64(0); i < num; i++ {
+		k, _ := r.ReadString()
+		v, _ := r.ReadUint()
+		ix[k] = uint(v)
+	}
+
+	return ix
+}