2 Commits 66952da263 ... 7387f4ffcc

Author SHA1 Message Date
  Adam 7387f4ffcc remove fixed ZTM Poznań from getting GTFS-RT data 1 month ago
  Adam 8b3f7c0635 refactor Traffic 1 month ago
10 changed files with 289 additions and 481 deletions
  1. 3 6
      config/config.go
  2. 7 114
      file/file.go
  3. 6 10
      main.go
  4. 15 23
      server/router.go
  5. 208 34
      traffic/access.go
  6. 40 42
      traffic/convert.go
  7. 10 4
      traffic/errors/errors.go
  8. 0 67
      traffic/feeds/feeds.go
  9. 0 181
      traffic/feeds/poznan_ztm.go
  10. 0 0
      traffic/structs/access.go

+ 3 - 6
config/config.go

@@ -1,8 +1,6 @@
 package config
 
 import (
-	"notabug.org/apiote/bimba_server/traffic/feeds"
-
 	"bufio"
 	"os"
 	"strings"
@@ -12,7 +10,7 @@ import (
 
 type Config struct {
 	FeedsPath    string
-	EnabledFeeds map[string]feeds.Feed
+	EnabledFeeds []string
 }
 
 type result struct {
@@ -46,12 +44,11 @@ func scan(input ...interface{}) interface{} {
 			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]
+				config.EnabledFeeds = append(config.EnabledFeeds, e)
 			}
 		}
 	}
@@ -65,7 +62,7 @@ func scan(input ...interface{}) interface{} {
 func Read(configPath string) (Config, error) {
 	input := gott.Tuple{result{
 		configPath: configPath,
-		config:     Config{EnabledFeeds: map[string]feeds.Feed{}},
+		config:     Config{EnabledFeeds: []string{}},
 	}}
 	output, err := gott.NewResult(input).
 		Bind(openFile).

+ 7 - 114
file/file.go

@@ -1,10 +1,6 @@
 package file
 
 import (
-	"notabug.org/apiote/bimba_server/config"
-	"notabug.org/apiote/bimba_server/traffic/feeds"
-	traffic_structs "notabug.org/apiote/bimba_server/traffic/structs"
-
 	"archive/tar"
 	"archive/zip"
 	"io"
@@ -12,7 +8,6 @@ import (
 	"log"
 	"os"
 	"path/filepath"
-	"sort"
 	"strings"
 	"time"
 
@@ -147,26 +142,22 @@ func MoveTraffic(gtfsFile, path, feedHome string) error {
 	return nil
 }
 
-func ListVersions(path string, location *time.Location) ([]feeds.Version, error) {
-	versions := []feeds.Version{}
+func ListVersions(path string, location *time.Location) ([]string, error) {
+	versions := []string{}
 	trafficFiles, err := ioutil.ReadDir(path)
 	if err != nil {
 		return nil, err
 	}
 	for _, txtFile := range trafficFiles {
 		if strings.Contains(txtFile.Name(), ".txz") {
-			versionString := strings.Replace(txtFile.Name(), ".txz", "", 1)
-			version, err := feeds.MakeVersion(versionString, location)
-			if err != nil {
-				return nil, err
-			}
-			versions = append(versions, version)
+			versionName := strings.Replace(txtFile.Name(), ".txz", "", 1)
+			versions = append(versions, versionName)
 		}
 	}
 	return versions, nil
 }
 
-func unpackTraffic(dataHome, feedName string) error {
+func UnpackTraffic(dataHome, feedName string) error {
 	path := filepath.Join(dataHome, feedName)
 	files, err := ioutil.ReadDir(path)
 	if err != nil {
@@ -220,117 +211,19 @@ func unpackTraffic(dataHome, feedName string) error {
 	return nil
 }
 
-func cleanOldVersions(dataHome, feedName string, location *time.Location) error {
-	now := time.Now().In(location)
-	path := filepath.Join(dataHome, feedName)
+func CleanOldVersions(path string, validVersions map[string]bool) error {
 	files, err := ioutil.ReadDir(path)
 	if err != nil {
 		return err
 	}
-	versions := []feeds.Version{}
-	versionsMap := map[string]feeds.Version{}
-	for _, file := range files {
-		name := file.Name()
-		versionString := strings.Replace(name, ".txz", "", 1)
-		versionsMap[versionString], err = feeds.MakeVersion(versionString, location)
-		if err != nil {
-			return err
-		}
-	}
-	for _, version := range versionsMap {
-		versions = append(versions, version)
-	}
-	sort.Slice(versions, func(i, j int) bool {
-		return versions[i].ValidFrom.Before(versions[j].ValidFrom)
-	})
-	validVersions := feeds.FindValidVersions(versions, now)
-	validVersionsMap := map[string]bool{}
-	for _, version := range validVersions {
-		validVersionsMap[version.String()] = true
-	}
 
 	for _, file := range files {
 		name := file.Name()
 		versionString := strings.Replace(name, ".txz", "", 1)
-		if !validVersionsMap[versionString] {
+		if !validVersions[versionString] {
 			filePath := filepath.Join(path, name)
 			os.RemoveAll(filePath)
 		}
 	}
 	return nil
 }
-
-func readIndexes(dataHome string, feedName string, codeIndex *traffic_structs.GlobalCodeIndex, versions []feeds.Version) {
-	ix := *codeIndex
-	for _, v := range versions {
-		versionCode := traffic_structs.Validity(v.String())
-		index := traffic_structs.ReadCodeIndex(dataHome, feedName, versionCode)
-		if ix[feedName] == nil {
-			ix[feedName] = traffic_structs.FeedCodeIndex{}
-		}
-		ix[feedName][versionCode] = index
-	}
-	*codeIndex = ix
-}
-
-func readCalendar(dataHome, feedName string, schedules *traffic_structs.GlobalCalendar, versions []feeds.Version) {
-	s := *schedules
-	for _, v := range versions {
-		versionCode := traffic_structs.Validity(v.String())
-		schedule := traffic_structs.ReadCalendar(dataHome, feedName, versionCode)
-		if s[feedName] == nil {
-			s[feedName] = traffic_structs.FeedCalendar{}
-		}
-		s[feedName][versionCode] = schedule
-	}
-	*schedules = s
-}
-
-func readVehicles(dataHome, feedName string, vehicles *traffic_structs.GlobalVehicles, versions []feeds.Version) {
-	vehicles_ := *vehicles
-	for _, v := range versions {
-		versionCode := traffic_structs.Validity(v.String())
-		versionVehicles := traffic_structs.ReadVehicles(dataHome, feedName, versionCode)
-		if vehicles_[feedName] == nil {
-			vehicles_[feedName] = traffic_structs.FeedVehicles{}
-		}
-		vehicles_[feedName][versionCode] = versionVehicles
-	}
-	*vehicles = vehicles_
-}
-
-func InitialiseTraffic(sigChan chan os.Signal, doneChan chan bool, cfg config.Config, codeIndex *traffic_structs.GlobalCodeIndex, calendar *traffic_structs.GlobalCalendar, vehicles *traffic_structs.GlobalVehicles, versions *feeds.GlobalVersions) {
-	for {
-		sig := <-sigChan
-		if sig == os.Interrupt {
-			break
-		}
-		allVersions := feeds.GlobalVersions{}
-		for _, feed := range cfg.EnabledFeeds {
-			feedHome := filepath.Join(cfg.FeedsPath, feed.String())
-
-			err := unpackTraffic(cfg.FeedsPath, feed.String())
-			if err != nil {
-				log.Printf("while unpacking TRAFFIC in feed %s: %v\n", feed, err)
-				continue
-			}
-			err = cleanOldVersions(cfg.FeedsPath, feed.String(), feed.GetLocation())
-			if err != nil {
-				log.Printf("while cleaning old TRAFFIC versions in feed %s: %v\n", feed, err)
-				continue
-			}
-			feedVersions, err := ListVersions(feedHome, feed.GetLocation())
-			if err != nil {
-				log.Printf("while listing TRAFFIC versions in feed %s: %v\n", feed, err)
-				continue
-			}
-
-			allVersions[feed.String()] = feedVersions
-			readIndexes(cfg.FeedsPath, feed.String(), codeIndex, feedVersions)
-			readCalendar(cfg.FeedsPath, feed.String(), calendar, feedVersions)
-			readVehicles(cfg.FeedsPath, feed.String(), vehicles, feedVersions)
-		}
-		*versions = allVersions
-	}
-	doneChan <- true
-}

+ 6 - 10
main.go

@@ -2,11 +2,8 @@ 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"
@@ -33,9 +30,12 @@ func main() {
 		os.Exit(1)
 	}
 
+	t := traffic.Traffic{}
+	traffic.EnableFeeds(cfg, &t)
+
 	switch command {
 	case "convert":
-		err := traffic.Prepare(cfg, -1) // todo Bimba PID
+		err := traffic.Prepare(cfg, t, -1) // todo Bimba PID
 		if err != nil {
 			log.Println(err)
 			os.Exit(1)
@@ -44,13 +44,9 @@ func main() {
 		c := make(chan os.Signal, 1)
 		d := make(chan bool)
 		signal.Notify(c, os.Interrupt, syscall.SIGUSR1)
-		codeIndexes := &traffic_structs.GlobalCodeIndex{}
-		versions := &feeds.GlobalVersions{}
-		calendar := &traffic_structs.GlobalCalendar{}
-		vehicles := &traffic_structs.GlobalVehicles{}
-		go file.InitialiseTraffic(c, d, cfg, codeIndexes, calendar, vehicles, versions)
+		go traffic.Initialise(c, d, cfg, &t)
 		c <- syscall.SIGUSR1
-		srv := server.Route(cfg, codeIndexes, calendar, vehicles, versions)
+		srv := server.Route(cfg, &t)
 		<-d
 		if err := srv.Shutdown(context.Background()); err != nil {
 			panic(err)

+ 15 - 23
server/router.go

@@ -5,8 +5,6 @@ import (
 	"notabug.org/apiote/bimba_server/config"
 	"notabug.org/apiote/bimba_server/traffic"
 	traffic_errors "notabug.org/apiote/bimba_server/traffic/errors"
-	"notabug.org/apiote/bimba_server/traffic/feeds"
-	traffic_structs "notabug.org/apiote/bimba_server/traffic/structs"
 
 	"log"
 	"net/http"
@@ -30,9 +28,9 @@ func handleStops(w http.ResponseWriter, r *http.Request, feedName string) {
 	// todo query in nameIndex
 }
 
-func handleDepartures(w http.ResponseWriter, r *http.Request, feedName string, cfg config.Config, versions []feeds.Version, codeIndex traffic_structs.FeedCodeIndex, calendar traffic_structs.FeedCalendar, vehicles traffic_structs.FeedVehicles) {
+func handleDepartures(w http.ResponseWriter, r *http.Request, feedName string, cfg config.Config, t *traffic.Traffic) {
 	r.ParseForm()
-	code := traffic_structs.ID(r.Form.Get("code"))
+	code := traffic.ID(r.Form.Get("code"))
 	if code == "" {
 		sendError(w, r, "code", "EMPTY", 400)
 		return
@@ -41,20 +39,20 @@ func handleDepartures(w http.ResponseWriter, r *http.Request, feedName string, c
 	// todo line := r.Form.Get("line")
 	// todo limit := r.Form.Get("limit")
 	limit := 12
-	versionCode := traffic_structs.Validity("")
-	departuresType := traffic_structs.DEPARTURES_FULL
+	versionCode := traffic.Validity("")
+	departuresType := traffic.DEPARTURES_FULL
 	if dateString == "" {
-		feedNow := time.Now().In(cfg.EnabledFeeds[feedName].GetLocation())
-		dateString = feedNow.Format(traffic_structs.DateFormat)
-		departuresType = traffic_structs.DEPARTURES_HYBRID
+		feedNow := time.Now().In(t.Feeds[feedName].GetLocation())
+		dateString = feedNow.Format(traffic.DateFormat)
+		departuresType = traffic.DEPARTURES_HYBRID
 	}
-	date, err := time.ParseInLocation(traffic_structs.DateFormat, dateString, cfg.EnabledFeeds[feedName].GetLocation())
+	date, err := time.ParseInLocation(traffic.DateFormat, dateString, t.Feeds[feedName].GetLocation())
 	if err != nil {
 		sendError(w, r, "date", dateString, 400)
 	}
-	for _, v := range versions {
+	for _, v := range t.Versions[feedName] {
 		if !v.ValidFrom.After(date) && !date.After(v.ValidTill) {
-			versionCode = traffic_structs.Validity(v.String())
+			versionCode = traffic.Validity(v.String())
 		}
 	}
 
@@ -63,9 +61,7 @@ func handleDepartures(w http.ResponseWriter, r *http.Request, feedName string, c
 		return
 	}
 
-	ix := codeIndex[versionCode]
-	cal := calendar[versionCode]
-	veh := vehicles[versionCode]
+	ix := t.CodeIndexes[feedName][versionCode]
 
 	stopCodePresent := ix[code]
 	if !stopCodePresent.Present {
@@ -73,7 +69,7 @@ func handleDepartures(w http.ResponseWriter, r *http.Request, feedName string, c
 		return
 	}
 
-	departures, err := traffic.GetDepartures(code, cfg.FeedsPath, feedName, versionCode, ix, cal, veh, date, departuresType)
+	departures, err := traffic.GetDepartures(code, cfg.FeedsPath, feedName, versionCode, t, date, departuresType)
 	if err != nil {
 		if _, ok := err.(traffic_errors.NoSchedule); ok {
 			sendError(w, r, "date", dateString, 404)
@@ -136,7 +132,7 @@ func send500(w http.ResponseWriter, r *http.Request, message string) {
 	w.Write(b)
 }
 
-func Route(cfg config.Config, codeIndexes *traffic_structs.GlobalCodeIndex, calendar *traffic_structs.GlobalCalendar, vehicles *traffic_structs.GlobalVehicles, versions *feeds.GlobalVersions) *http.Server {
+func Route(cfg config.Config, traffic *traffic.Traffic) *http.Server {
 	srv := &http.Server{Addr: ":51354"}
 
 	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
@@ -145,23 +141,19 @@ func Route(cfg config.Config, codeIndexes *traffic_structs.GlobalCodeIndex, cale
 		} else {
 			path := strings.Split(r.URL.Path[1:], "/")
 			feedName := path[0]
-			v := *versions
-			if v[feedName] == nil {
+			if traffic.Versions[feedName] == nil {
 				sendError(w, r, "feed", feedName, 404)
 				return
 			}
 			if len(path) == 1 {
 				handleFeed(w, r, feedName)
 			} else {
-				ix := *codeIndexes
-				c := *calendar
-				veh := *vehicles
 				resource := path[1]
 				switch resource {
 				case "stops":
 					handleStops(w, r, feedName)
 				case "departures":
-					handleDepartures(w, r, feedName, cfg, v[feedName], ix[feedName], c[feedName], veh[feedName])
+					handleDepartures(w, r, feedName, cfg, traffic)
 				default:
 					sendError(w, r, "resource", resource, 404)
 				}

+ 208 - 34
traffic/access.go

@@ -2,9 +2,10 @@ package traffic
 
 import (
 	api_structs "notabug.org/apiote/bimba_server/api/structs"
+	"notabug.org/apiote/bimba_server/config"
+	"notabug.org/apiote/bimba_server/file"
 	"notabug.org/apiote/bimba_server/gtfs_rt"
 	traffic_errors "notabug.org/apiote/bimba_server/traffic/errors"
-	traffic_structs "notabug.org/apiote/bimba_server/traffic/structs"
 
 	"encoding/hex"
 	"log"
@@ -23,9 +24,10 @@ type getDeparturesResult struct {
 	StopOffset     uint
 	Date           time.Time
 	TimetableHome  string
-	Calendar       []traffic_structs.Schedule
-	DeparturesType traffic_structs.DeparturesType
-	Vehicles       traffic_structs.Vehicles
+	Calendar       []Schedule
+	DeparturesType DeparturesType
+	Vehicles       Vehicles
+	Feed           Feed
 
 	Location          *time.Location
 	Datetime          time.Time
@@ -34,17 +36,17 @@ type getDeparturesResult struct {
 	TodaySchedule     string
 	YesterdaySchedule string
 	StopsFile         *os.File
-	Stop              traffic_structs.Stop
+	Stop              Stop
 	TripsFile         *os.File
 
 	Departures []api_structs.Departure
 }
 
-var lastUpdatedGtfsRt uint64 = 0
+var lastUpdatedGtfsRt = map[string]uint64{}
 
-func findSchedule(home string, time time.Time, calendar []traffic_structs.Schedule) (string, error) {
+func findSchedule(home string, time time.Time, calendar []Schedule) (string, error) {
 	weekday := uint8(1 << time.Weekday())
-	date := time.Format(traffic_structs.DateFormat)
+	date := time.Format(DateFormat)
 	for _, schedule := range calendar {
 		if schedule.StartDate <= date && date <= schedule.EndDate && (schedule.Weekdays&weekday != 0) {
 			return schedule.ScheduleID, nil
@@ -53,12 +55,12 @@ func findSchedule(home string, time time.Time, calendar []traffic_structs.Schedu
 	return "", traffic_errors.NoSchedule{Date: date}
 }
 
-func getRealtimeOffset(tripID string, stepSequence int) (gtfs_rt.Update, error) {
-	updates, lastUpdated, err := gtfs_rt.GetRt(lastUpdatedGtfsRt, []string{"https://ztm.poznan.pl/en/dla-deweloperow/getGtfsRtFile/?file=feeds.pb"}, "poznan_ztm")
+func getRealtimeOffset(tripID string, stepSequence int, feed Feed) (gtfs_rt.Update, error) {
+	updates, lastUpdated, err := gtfs_rt.GetRt(lastUpdatedGtfsRt[feed.String()], feed.RealtimeFeeds(), feed.String())
 	if err != nil {
 		return gtfs_rt.Update{}, err
 	}
-	lastUpdatedGtfsRt = lastUpdated
+	lastUpdatedGtfsRt[feed.String()] = lastUpdated
 	update := updates[tripID]
 	return update, nil
 }
@@ -93,7 +95,7 @@ func getDayOffset(t1, t2 time.Time) int8 {
 }
 
 func marshalStopOrder(tripOffset uint, stopOrder int) (string, error) {
-	order := traffic_structs.StopOrder{
+	order := StopOrder{
 		TripOffset: tripOffset,
 		Order:      stopOrder,
 	}
@@ -146,7 +148,7 @@ func recoverYesterdaySchedule(input ...interface{}) (interface{}, error) {
 	result := input[0].(getDeparturesResult)
 	err := input[1].(error)
 
-	dayBefore := result.Date.AddDate(0, 0, -1).Format(traffic_structs.DateFormat)
+	dayBefore := result.Date.AddDate(0, 0, -1).Format(DateFormat)
 	if err, ok := err.(traffic_errors.NoSchedule); ok && err.Date == dayBefore {
 		result.YesterdaySchedule = ""
 		return gott.Tuple{result}, nil
@@ -172,7 +174,7 @@ func seekStops(input ...interface{}) (interface{}, error) {
 func unmarshalStop(input ...interface{}) (interface{}, error) {
 	result := input[0].(getDeparturesResult)
 
-	result.Stop = traffic_structs.Stop{}
+	result.Stop = Stop{}
 	err := bare.UnmarshalReader(result.StopsFile, &result.Stop)
 	result.StopsFile.Close()
 	return result, err
@@ -195,7 +197,7 @@ func readTrips(input ...interface{}) (interface{}, error) {
 		if err != nil {
 			return result, err
 		}
-		trip := traffic_structs.Trip{}
+		trip := Trip{}
 		err = bare.UnmarshalReader(result.TripsFile, &trip)
 		if err != nil {
 			return result, err
@@ -214,7 +216,7 @@ func readTrips(input ...interface{}) (interface{}, error) {
 		for _, trafficDeparture := range trip.Departures {
 			if trafficDeparture.StopSeq == order.Order {
 				order.TripID = trip.ID
-				apiDeparture, err := readDeparture(trafficDeparture, date, result, order, trip)
+				apiDeparture, err := readDeparture(trafficDeparture, date, result, order, trip, result.Feed)
 				if apiDeparture != nil {
 					departures = append(departures, *apiDeparture)
 				}
@@ -226,11 +228,10 @@ func readTrips(input ...interface{}) (interface{}, error) {
 			}
 		}
 		if !found {
-			return result, traffic_errors.NoStopOrder{StopOrder: traffic_structs.StopOrder{
-				TripID:     trip.ID,
-				TripOffset: order.TripOffset,
-				Order:      order.Order,
-			}}
+			return result, traffic_errors.NoStopOrder{
+				TripID: trip.ID,
+				Order:  order.Order,
+			}
 		}
 	}
 	result.Departures = departures
@@ -238,9 +239,9 @@ func readTrips(input ...interface{}) (interface{}, error) {
 	return result, nil
 }
 
-func readDeparture(trafficDeparture traffic_structs.Departure, date time.Time,
-	result getDeparturesResult, order traffic_structs.StopOrder,
-	trip traffic_structs.Trip) (*api_structs.Departure, error) {
+func readDeparture(trafficDeparture Departure, date time.Time,
+	result getDeparturesResult, order StopOrder,
+	trip Trip, feed Feed) (*api_structs.Departure, error) {
 
 	departureTime, err := calculateGtfsTime(trafficDeparture.Time, 0, date,
 		result.Location)
@@ -251,8 +252,8 @@ func readDeparture(trafficDeparture traffic_structs.Departure, date time.Time,
 	var departure *api_structs.Departure = nil
 	if departureTime.After(result.Midnight) {
 		update := gtfs_rt.Update{}
-		if result.DeparturesType == traffic_structs.DEPARTURES_HYBRID {
-			update, err = getRealtimeOffset(order.TripID, order.Order)
+		if result.DeparturesType == DEPARTURES_HYBRID {
+			update, err = getRealtimeOffset(order.TripID, order.Order, feed)
 			if err != nil {
 				log.Printf("while getting realtime departures: %v\n", err)
 			}
@@ -262,7 +263,7 @@ func readDeparture(trafficDeparture traffic_structs.Departure, date time.Time,
 		if err != nil {
 			return nil, err
 		}
-		if result.DeparturesType == traffic_structs.DEPARTURES_FULL ||
+		if result.DeparturesType == DEPARTURES_FULL ||
 			departureTime.After(result.MinuteB4Datetime) {
 			status := api_structs.VEHICLE_IN_TRANSIT
 			timeToArrival := departureTime.Sub(result.Datetime).Minutes()
@@ -279,9 +280,9 @@ func readDeparture(trafficDeparture traffic_structs.Departure, date time.Time,
 				return nil, err
 			}
 			vehicle := api_structs.Vehicle{
-				Position: olc.Encode(float64(update.Latitude), float64(update.Longitude), 11),
-				Capabilities: result.Vehicles[traffic_structs.ID(update.VehicleID)].Capabilities,
-				Speed: update.Speed,
+				Position:        olc.Encode(float64(update.Latitude), float64(update.Longitude), 11),
+				Capabilities:    result.Vehicles[ID(update.VehicleID)].Capabilities,
+				Speed:           update.Speed,
 				CongestionLevel: update.CongestionLevel,
 				OccupancyStatus: update.OccupancyStatus,
 			}
@@ -335,10 +336,146 @@ func closeFiles(input ...interface{}) (interface{}, error) {
 	return result, err
 }
 
-func GetDepartures(stopCode traffic_structs.ID, dataHome, feedName string,
-	versionCode traffic_structs.Validity, codeIndex traffic_structs.CodeIndex,
-	calendar []traffic_structs.Schedule, vehicles traffic_structs.Vehicles, date time.Time,
-	departuresType traffic_structs.DeparturesType) ([]api_structs.Departure, error) {
+func unmarshalCodeIndex(timetableHome string) CodeIndex {
+	ixFile, _ := os.Open(filepath.Join(timetableHome, "ix_stop_codes.bare"))
+	defer ixFile.Close()
+
+	ix := CodeIndex{}
+	r := bare.NewReader(ixFile)
+	num, _ := r.ReadUint()
+	for i := uint64(0); i < num; i++ {
+		k, _ := r.ReadString()
+		v, _ := r.ReadUint()
+		ix[ID(k)] = PresentCode{
+			Present: true,
+			Offset:  uint(v),
+		}
+	}
+
+	return ix
+}
+
+func readIndexes(feedHome string, versions []Version) FeedCodeIndex {
+	codeIndex := FeedCodeIndex{}
+	for _, v := range versions {
+		validity := Validity(v.String())
+		timetableHome := filepath.Join(feedHome, string(validity))
+		index := unmarshalCodeIndex(timetableHome)
+		codeIndex[validity] = index
+	}
+	return codeIndex
+}
+
+func unmarshalCalendar(timetableHome string) []Schedule {
+	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 readCalendar(feedHome string, versions []Version) FeedCalendar {
+	calendars := FeedCalendar{}
+	for _, v := range versions {
+		validity := Validity(v.String())
+		timetableHome := filepath.Join(feedHome, string(validity))
+		schedule := unmarshalCalendar(timetableHome)
+		calendars[validity] = schedule
+	}
+	return calendars
+}
+
+func unmarshalVehicles(timetableHome string) Vehicles {
+	vehiclesFile, _ := os.Open(filepath.Join(timetableHome, "vehicles.bare"))
+	defer vehiclesFile.Close()
+
+	vehicles := Vehicles{}
+	var err error = nil
+	for err == nil {
+		vehicle := Vehicle{}
+		err = bare.UnmarshalReader(vehiclesFile, &vehicle)
+		if err == nil {
+			vehicles[vehicle.Id] = vehicle
+		}
+	}
+	return vehicles
+}
+
+func readVehicles(feedHome string, versions []Version) FeedVehicles {
+	vehicles := FeedVehicles{}
+	for _, v := range versions {
+		validity := Validity(v.String())
+		timetableHome := filepath.Join(feedHome, string(validity))
+		versionVehicles := unmarshalVehicles(timetableHome)
+		vehicles[validity] = versionVehicles
+	}
+	return vehicles
+}
+
+func EnableFeeds(cfg config.Config, traffic *Traffic) {
+	feedsMap := RegisterFeeds()
+	feeds := map[string]Feed{}
+	for _, enabledFeed := range cfg.EnabledFeeds {
+		feeds[enabledFeed] = feedsMap[enabledFeed]
+	}
+	traffic.Feeds = feeds
+}
+
+func Initialise(sigChan chan os.Signal, doneChan chan bool, cfg config.Config, traffic *Traffic) {
+	for {
+		sig := <-sigChan
+		if sig == os.Interrupt {
+			break
+		}
+		allVersions := GlobalVersions{}
+		codeIndexes := GlobalCodeIndex{}
+		calendars := GlobalCalendar{}
+		vehicles := GlobalVehicles{}
+		for _, feed := range traffic.Feeds {
+			feedHome := filepath.Join(cfg.FeedsPath, feed.String())
+			err := file.UnpackTraffic(cfg.FeedsPath, feed.String())
+			if err != nil {
+				log.Printf("while unpacking TRAFFIC in feed %s: %v\n", feed, err)
+				continue
+			}
+			err = CleanOldVersions(cfg, feed)
+			if err != nil {
+				log.Printf("while cleaning old TRAFFIC versions in feed %s: %v\n", feed, err)
+				continue
+			}
+			feedVersions, err := ListVersions(cfg, feed)
+			if err != nil {
+				log.Printf("while listing TRAFFIC versions in feed %s: %v\n", feed, err)
+				continue
+			}
+
+			feedName := feed.String()
+			allVersions[feedName] = feedVersions
+			codeIndexes[feedName] = readIndexes(feedHome, feedVersions)
+			calendars[feedName] = readCalendar(feedHome, feedVersions)
+			vehicles[feedName] = readVehicles(feedHome, feedVersions)
+		}
+		traffic.CodeIndexes = codeIndexes
+		traffic.Versions = allVersions
+		traffic.Calendars = calendars
+		traffic.Vehicles = vehicles
+	}
+	doneChan <- true
+}
+
+func GetDepartures(stopCode ID, dataHome, feedName string,
+	versionCode Validity, traffic *Traffic, date time.Time,
+	departuresType DeparturesType) ([]api_structs.Departure, error) {
+
+	codeIndex := traffic.CodeIndexes[feedName][versionCode]
+	calendar := traffic.Calendars[feedName][versionCode]
+	vehicles := traffic.Vehicles[feedName][versionCode]
 
 	result := getDeparturesResult{
 		StopOffset:     codeIndex[stopCode].Offset,
@@ -347,6 +484,7 @@ func GetDepartures(stopCode traffic_structs.ID, dataHome, feedName string,
 		Calendar:       calendar,
 		DeparturesType: departuresType,
 		Vehicles:       vehicles,
+		Feed:           traffic.Feeds[feedName],
 	}
 
 	r, e := gott.NewResult(result).
@@ -369,4 +507,40 @@ func GetDepartures(stopCode traffic_structs.ID, dataHome, feedName string,
 	} else {
 		return r.(getDeparturesResult).Departures, nil
 	}
+
+}
+
+func GetTripFromStop(tripOffset uint, order int) {
+	// todo return []{stopCode, stopName, time}
+}
+
+func GetStop(stopCode string) {
+	// todo return {stopCode, stopName, stopDescription (not-yet), Zone, Coordinates}
+}
+
+func QueryStops(stopNameOrCode string) {
+	// todo return []{stopCode, stopName, ChangeOptions, Zone}
+}
+
+func CleanOldVersions(cfg config.Config, feed Feed) error {
+	allVersions, err := ListVersions(cfg, feed)
+	if err != nil {
+		return err
+	}
+	now := time.Now().In(feed.GetLocation())
+	versionsMap := map[string]Version{}
+	for _, version := range allVersions {
+		versionsMap[version.String()] = version
+	}
+
+	sort.Slice(allVersions, func(i, j int) bool {
+		return allVersions[i].ValidFrom.Before(allVersions[j].ValidFrom)
+	})
+	validVersions := FindValidVersions(allVersions, now)
+	validVersionsMap := map[string]bool{}
+	for _, version := range validVersions {
+		validVersionsMap[version.String()] = true
+	}
+	err = file.CleanOldVersions(FeedPath(cfg, feed), validVersionsMap)
+	return err
 }

+ 40 - 42
traffic/convert.go

@@ -3,8 +3,6 @@ package traffic
 import (
 	"notabug.org/apiote/bimba_server/config"
 	"notabug.org/apiote/bimba_server/file"
-	"notabug.org/apiote/bimba_server/traffic/feeds"
-	"notabug.org/apiote/bimba_server/traffic/structs"
 
 	"bufio"
 	"encoding/csv"
@@ -29,15 +27,15 @@ type result struct {
 	config             config.Config
 	pid                int
 	tmpPath            string
-	feed               feeds.Feed
+	feed               Feed
 	feedName           string
 	location           *time.Location
 	tmpFeedPath        string
 	homeFeedPath       string
-	downloadedVersions []feeds.Version
-	allVersions        []feeds.Version
+	downloadedVersions []Version
+	allVersions        []Version
 	gtfsFilenames      []string
-	missingVersions    []feeds.Version
+	missingVersions    []Version
 }
 
 // helper functions
@@ -62,7 +60,7 @@ func isNumber(s string) bool {
 	return true
 }
 
-func contains(array []feeds.Version, item feeds.Version) bool {
+func contains(array []Version, item Version) bool {
 	for _, i := range array {
 		if item.ValidFrom.Equal(i.ValidFrom) && i.ValidTill.Equal(i.ValidTill) {
 			return true
@@ -74,14 +72,14 @@ func contains(array []feeds.Version, item feeds.Version) bool {
 // converting functions
 
 // warning (poznan_ztm): pickup_type and drop_off_type is never 3; and seemingly no way to deduce this fact
-func convertDepartures(path string) (map[string][]structs.Departure, map[string][]structs.StopOrder, error) {
+func convertDepartures(path string) (map[string][]Departure, map[string][]StopOrder, error) {
 	file, err := os.Open(filepath.Join(path, "stop_times.txt"))
 	if err != nil {
 		return nil, nil, err
 	}
 	defer file.Close()
 
-	departures := map[string][]structs.Departure{}
+	departures := map[string][]Departure{}
 
 	r := csv.NewReader(bufio.NewReader(file))
 	header, err := r.Read()
@@ -93,10 +91,10 @@ func convertDepartures(path string) (map[string][]structs.Departure, map[string]
 		fields[headerField] = i
 	}
 
-	tripsThroughStop := map[string][]structs.StopOrder{}
+	tripsThroughStop := map[string][]StopOrder{}
 
 	for {
-		departure := structs.Departure{}
+		departure := Departure{}
 		record, err := r.Read()
 		if err == io.EOF {
 			break
@@ -107,7 +105,7 @@ func convertDepartures(path string) (map[string][]structs.Departure, map[string]
 
 		stopTrips := tripsThroughStop[record[fields["stop_id"]]]
 		if stopTrips == nil {
-			stopTrips = []structs.StopOrder{}
+			stopTrips = []StopOrder{}
 		}
 
 		tripID := record[fields["trip_id"]]
@@ -115,7 +113,7 @@ func convertDepartures(path string) (map[string][]structs.Departure, map[string]
 		fmt.Sscanf(record[fields["pickup_type"]], "%d", &departure.Pickup)
 		fmt.Sscanf(record[fields["drop_off_type"]], "%d", &departure.Dropoff)
 
-		stopTrips = append(stopTrips, structs.StopOrder{
+		stopTrips = append(stopTrips, StopOrder{
 			TripID: tripID,
 			Order:  departure.StopSeq,
 		})
@@ -126,7 +124,7 @@ func convertDepartures(path string) (map[string][]structs.Departure, map[string]
 		departure.Time = hours*60 + minutes
 
 		if departures[tripID] == nil {
-			departures[tripID] = []structs.Departure{}
+			departures[tripID] = []Departure{}
 		}
 		departures[tripID] = append(departures[tripID], departure)
 	}
@@ -154,7 +152,7 @@ func convertCalendar(path string, resultFile *os.File) error {
 	}
 
 	for {
-		schedule := structs.Schedule{}
+		schedule := Schedule{}
 		record, err := r.Read()
 		if err == io.EOF {
 			break
@@ -223,7 +221,7 @@ func convertCalendarDates(path string, resultFile *os.File) error {
 	}
 
 	for {
-		schedule := structs.Schedule{}
+		schedule := Schedule{}
 		record, err := r.Read()
 		if err == io.EOF {
 			break
@@ -295,7 +293,7 @@ func convertLines(path string) (map[string]string, error) {
 	names := map[string]string{}
 
 	for {
-		line := structs.Line{}
+		line := Line{}
 		record, err := r.Read()
 		if err == io.EOF {
 			break
@@ -326,7 +324,7 @@ func convertLines(path string) (map[string]string, error) {
 	return names, nil
 }
 
-func convertTrips(path string, departures map[string][]structs.Departure, lineNames map[string]string) (map[string]uint, map[string]structs.ChangeOption, error) {
+func convertTrips(path string, departures map[string][]Departure, lineNames map[string]string) (map[string]uint, map[string]ChangeOption, error) {
 	file, err := os.Open(filepath.Join(path, "trips.txt"))
 	if err != nil {
 		return nil, nil, err
@@ -352,10 +350,10 @@ func convertTrips(path string, departures map[string][]structs.Departure, lineNa
 	var offset uint = 0
 	tripsOffset := map[string]uint{}
 
-	tripDirections := map[string]structs.ChangeOption{}
+	tripDirections := map[string]ChangeOption{}
 
 	for {
-		trip := structs.Trip{}
+		trip := Trip{}
 		record, err := r.Read()
 		if err == io.EOF {
 			break
@@ -370,7 +368,7 @@ func convertTrips(path string, departures map[string][]structs.Departure, lineNa
 		trip.ScheduleID = record[fields["service_id"]]
 		trip.LineName = lineNames[record[fields["route_id"]]]
 
-		tripDirections[trip.ID] = structs.ChangeOption{
+		tripDirections[trip.ID] = ChangeOption{
 			LineID:   record[fields["route_id"]],
 			Headsign: trip.Headsign,
 		}
@@ -389,7 +387,7 @@ func convertTrips(path string, departures map[string][]structs.Departure, lineNa
 	return tripsOffset, tripDirections, nil
 }
 
-func convertStops(path string, tripsThroughStop map[string][]structs.StopOrder, tripsOffsets map[string]uint, tripDirections map[string]structs.ChangeOption, lineNames map[string]string) (map[string]uint, map[string][]uint, error) {
+func convertStops(path string, tripsThroughStop map[string][]StopOrder, tripsOffsets map[string]uint, tripDirections map[string]ChangeOption, lineNames map[string]string) (map[string]uint, map[string][]uint, error) {
 	file, err := os.Open(filepath.Join(path, "stops.txt"))
 	if err != nil {
 		return nil, nil, err
@@ -417,7 +415,7 @@ func convertStops(path string, tripsThroughStop map[string][]structs.StopOrder,
 	stopsOffsetsByCode := map[string]uint{}
 
 	for {
-		stop := structs.Stop{}
+		stop := Stop{}
 		record, err := r.Read()
 		if err == io.EOF {
 			break
@@ -437,14 +435,14 @@ func convertStops(path string, tripsThroughStop map[string][]structs.StopOrder,
 		fmt.Sscanf(record[fields["stop_lon"]], "%f", &lon)
 		stop.Coordinates = olc.Encode(lat, lon, 11)
 
-		stop.ChangeOptions = []structs.ChangeOption{}
-		stop.Order = []structs.StopOrder{}
+		stop.ChangeOptions = []ChangeOption{}
+		stop.Order = []StopOrder{}
 		for _, stopTrip := range stopTrips {
-			changeOption := structs.ChangeOption{
+			changeOption := ChangeOption{
 				LineName: lineNames[tripDirections[stopTrip.TripID].LineID],
 				Headsign: tripDirections[stopTrip.TripID].Headsign,
 			}
-			stopOrder := structs.StopOrder{
+			stopOrder := StopOrder{
 				TripOffset: tripsOffsets[stopTrip.TripID],
 				Order:      stopTrip.Order,
 			}
@@ -471,15 +469,15 @@ func convertStops(path string, tripsThroughStop map[string][]structs.StopOrder,
 }
 
 func createStopNameIndex(path string, stopsOffsetsByName map[string][]uint) error {
-	root := structs.TrieElement{
+	root := TrieElement{
 		Letter:     '.',
-		Children:   map[rune]*structs.TrieElement{},
+		Children:   map[rune]*TrieElement{},
 		StopOffset: nil,
 	}
 
 	for stopName, offsets := range stopsOffsetsByName {
 		isNewSegment := true
-		parents := []*structs.TrieElement{&root}
+		parents := []*TrieElement{&root}
 		for _, letter := range strings.ToLower(stopName) {
 			if !unicode.IsLetter(letter) {
 				if isNewSegment {
@@ -489,13 +487,13 @@ func createStopNameIndex(path string, stopsOffsetsByName map[string][]uint) erro
 				isNewSegment = true
 				continue
 			}
-			newParents := []*structs.TrieElement{}
+			newParents := []*TrieElement{}
 			for _, parent := range parents {
 				child := parent.Children[letter]
 				if child == nil {
-					newElement := &structs.TrieElement{
+					newElement := &TrieElement{
 						Letter:   letter,
-						Children: map[rune]*structs.TrieElement{},
+						Children: map[rune]*TrieElement{},
 					}
 					parent.Children[letter] = newElement
 					child = newElement
@@ -519,12 +517,12 @@ func createStopNameIndex(path string, stopsOffsetsByName map[string][]uint) erro
 	return nil
 }
 
-func writeStopNameIndex(root *structs.TrieElement, startingDepth uint, result *os.File) error {
+func writeStopNameIndex(root *TrieElement, startingDepth uint, result *os.File) error {
 	depth := startingDepth
 	prefix := string(root.Letter)
 	element := root
 	for len(element.Children) == 1 && (element.StopOffset == nil || len(*element.StopOffset) == 0) {
-		var child *structs.TrieElement
+		var child *TrieElement
 		for _, v := range element.Children {
 			child = v
 		}
@@ -536,7 +534,7 @@ func writeStopNameIndex(root *structs.TrieElement, startingDepth uint, result *o
 	if element.StopOffset != nil {
 		offset = *element.StopOffset
 	}
-	trieIndexSegment := structs.TrieIndexSegment{
+	trieIndexSegment := TrieIndexSegment{
 		Depth:      startingDepth,
 		Prefix:     prefix,
 		StopOffset: offset,
@@ -593,7 +591,7 @@ func createFeedHome(input ...interface{}) (interface{}, error) {
 
 func listDownloadedVersions(input ...interface{}) (interface{}, error) {
 	args := input[0].(result)
-	v, err := file.ListVersions(args.homeFeedPath, args.location)
+	v, err := ListVersions(args.config, args.feed)
 	args.downloadedVersions = v
 	return gott.Tuple{args}, err
 }
@@ -607,7 +605,7 @@ func getAllVersions(input ...interface{}) (interface{}, error) {
 
 func findValidVersions(input ...interface{}) interface{} {
 	args := input[0].(result)
-	dateValidVersions := []feeds.Version{}
+	dateValidVersions := []Version{}
 	now := time.Now().In(args.location)
 	for _, version := range args.allVersions {
 		if version.ValidFrom.Before(now) && now.Before(version.ValidTill) {
@@ -620,9 +618,9 @@ func findValidVersions(input ...interface{}) interface{} {
 	sort.Slice(args.downloadedVersions, func(i, j int) bool {
 		return args.downloadedVersions[i].ValidFrom.Before(args.downloadedVersions[j].ValidFrom)
 	})
-	validVersions := feeds.FindValidVersions(dateValidVersions, now)
+	validVersions := FindValidVersions(dateValidVersions, now)
 
-	missingVersions := []feeds.Version{}
+	missingVersions := []Version{}
 	for _, version := range validVersions {
 		if !contains(args.downloadedVersions, version) {
 			missingVersions = append(missingVersions, version)
@@ -735,8 +733,8 @@ func signal(input ...interface{}) (interface{}, error) {
 	return gott.Tuple{args}, nil
 }
 
-func Prepare(cfg config.Config, bimbaPid int) error {
-	for _, feed := range cfg.EnabledFeeds {
+func Prepare(cfg config.Config, t Traffic, bimbaPid int) error {
+	for _, feed := range t.Feeds {
 		r := gott.Tuple{result{
 			config:   cfg,
 			pid:      bimbaPid,

+ 10 - 4
traffic/errors/errors.go

@@ -2,8 +2,6 @@ package errors
 
 import (
 	"fmt"
-
-	"notabug.org/apiote/bimba_server/traffic/structs"
 )
 
 type NoSchedule struct {
@@ -15,9 +13,17 @@ func (e NoSchedule) Error() string {
 }
 
 type NoStopOrder struct {
-	StopOrder structs.StopOrder
+	TripID string
+	Order int
 }
 
 func (e NoStopOrder) Error() string {
-	return fmt.Sprintf("No stopOrder for trip %s, order %d", e.StopOrder.TripID, e.StopOrder.Order)
+	return fmt.Sprintf("No stopOrder for trip %s, order %d", e.TripID, e.Order)
+}
+
+type VersionError struct {
+	Msg string
+}
+func (v VersionError) Error() string {
+	return v.Msg
 }

+ 0 - 67
traffic/feeds/feeds.go

@@ -1,67 +0,0 @@
-package feeds
-
-import (
-	"fmt"
-	"strings"
-	"time"
-)
-
-type Feed interface {
-	ConvertVehicles(string) error
-	GetVersions(time.Time) ([]Version, error)
-	GetLocation() *time.Location
-	String() string
-}
-
-type Version struct {
-	Link      string
-	ValidFrom time.Time
-	ValidTill time.Time
-}
-
-func (v Version) String() string {
-	return v.ValidFrom.Format("20060102") + "_" + v.ValidTill.Format("20060102")
-}
-
-func RegisterFeeds() map[string]Feed {
-	return map[string]Feed{
-		"poznan_ztm": ZtmPoznan{},
-	}
-}
-
-func MakeVersion(s string, location *time.Location) (Version, error) {
-	version := Version{}
-	versionDates := strings.Split(s, "_")
-	if len(versionDates) != 2 {
-		return version, fmt.Errorf("invalid version string %s, not /.*_.*/", s)
-	}
-	validFrom, err := time.ParseInLocation("20060102", versionDates[0], location)
-	if err != nil {
-		return version, fmt.Errorf("invalid first part in %s: %w", s, err)
-	}
-	validTill, err := time.ParseInLocation("20060102", versionDates[1], location)
-	if err != nil {
-		return version, fmt.Errorf("invalid second part in %s: %w", s, err)
-	}
-	version.ValidFrom = validFrom
-	version.ValidTill = validTill
-	return version, nil
-}
-
-func FindValidVersions(versions []Version, now time.Time) []Version {
-	i := 0
-	for _, version := range versions {
-		if version.ValidFrom.Before(now) {
-			i++
-		} else {
-			break
-		}
-	}
-	if i > 0 {
-		i = i - 1
-	}
-	validVersions := versions[i:]
-	return validVersions
-}
-
-type GlobalVersions map[string][]Version

+ 0 - 181
traffic/feeds/poznan_ztm.go

@@ -1,181 +0,0 @@
-package feeds
-
-import (
-	"notabug.org/apiote/bimba_server/traffic/structs"
-
-	"encoding/csv"
-	"fmt"
-	"golang.org/x/net/html"
-	"io"
-	"net/http"
-	"os"
-	"path/filepath"
-	"regexp"
-	"strings"
-	"time"
-
-	"git.sr.ht/~sircmpwn/go-bare"
-)
-
-type HtmlSelector struct {
-	tag   string
-	id    string
-	class string
-}
-
-type ZtmPoznan struct{}
-
-func (z ZtmPoznan) ConvertVehicles(path string) error {
-	url := "https://ztm.poznan.pl/en/dla-deweloperow/getGtfsRtFile/?file=vehicle_dictionary.csv"
-	response, err := http.Get(url)
-	if err != nil {
-		return fmt.Errorf("ConvertVehicles: cannot GET ‘%s’: %w", url, err)
-	}
-
-	result, err := os.Create(filepath.Join(path, "vehicles.bare"))
-	if err != nil {
-		return fmt.Errorf("ConvertVehicles: cannot create bare file: %w", err)
-	}
-	defer result.Close()
-
-	r := csv.NewReader(response.Body)
-	r.Comma = ','
-	header, err := r.Read()
-	if err != nil {
-		fmt.Println("Header read error")
-		return err
-	}
-	fieldsNum := len(header)
-	if fieldsNum != 9 {
-		return VersionError{
-			Msg: fmt.Sprintf("number of fields %d != 9", fieldsNum),
-		}
-	}
-	fields := map[string]int{}
-	for i, headerField := range header {
-		fields[headerField] = i
-	}
-
-	for {
-		record, err := r.Read()
-		if err == io.EOF {
-			break
-		}
-		if err != nil {
-			return err
-		}
-
-		var capabilites uint8 = 0
-		for field, position := range fields {
-			if field != "vehicle" && record[position] != "0" {
-				capabilites = capabilites | (1 << (position - 1))
-			}
-		}
-
-		vehicle := structs.Vehicle{
-			Id:           structs.ID(record[0]),
-			Capabilities: capabilites,
-		}
-
-		bytes, err := bare.Marshal(&vehicle)
-		if err != nil {
-			return err
-		}
-		result.Write(bytes)
-	}
-	return nil
-}
-
-func (z ZtmPoznan) GetVersions(date time.Time) ([]Version, error) {
-	url := "https://ztm.poznan.pl/en/dla-deweloperow/gtfsFiles"
-	response, err := http.Get(url)
-	if err != nil {
-		return []Version{}, fmt.Errorf("GetVersions: cannot GET ‘%s’: %w", url, err)
-	}
-	doc, err := html.Parse(response.Body)
-	if err != nil {
-		return []Version{}, fmt.Errorf("GetVersions: cannot parse html: %w", err)
-	}
-	main := findNode(doc, HtmlSelector{
-		tag:   "div",
-		class: "main-content",
-	})
-	table := findNodes(main, HtmlSelector{
-		tag: "table",
-	})[1]
-	tbody := findNode(table, HtmlSelector{
-		tag: "tbody",
-	})
-	versions := []Version{}
-	for tr := tbody.FirstChild; tr != nil; tr = tr.NextSibling {
-		validityString := ""
-		link := ""
-		for td := tr.FirstChild; td != nil; td = td.NextSibling {
-			textNode := td.FirstChild
-			if textNode != nil {
-				if match, _ := regexp.MatchString("[0-9]{8}_[0-9]{8}", td.FirstChild.Data); match {
-					validityString = strings.Replace(textNode.Data, ".zip", "", 1)
-				}
-			}
-			linkA := findNode(td, HtmlSelector{tag: "a"})
-			if linkA != nil {
-				for _, a := range linkA.Attr {
-					if a.Key == "href" {
-						link = a.Val
-					}
-				}
-			}
-		}
-		if validityString != "" && link != "" {
-			version, err := MakeVersion(validityString, z.GetLocation())
-			if err != nil {
-				return nil, err
-			}
-			version.Link = "https://ztm.poznan.pl/" + link
-			versions = append(versions, version)
-		}
-	}
-	return versions, nil
-}
-
-func findNode(node *html.Node, selector HtmlSelector) *html.Node {
-	nodes := findNodes(node, selector)
-	if len(nodes) > 0 {
-		return nodes[0]
-	} else {
-		return nil
-	}
-}
-
-func findNodes(node *html.Node, selector HtmlSelector) []*html.Node {
-	if node == nil {
-		return []*html.Node{}
-	}
-	id := ""
-	class := ""
-	for _, a := range node.Attr {
-		if a.Key == "id" && selector.id != "" {
-			id = a.Val
-		} else if a.Key == "class" && selector.class != "" {
-			class = a.Val
-		}
-	}
-	if node.Data == selector.tag && id == selector.id && class == selector.class {
-		return []*html.Node{node}
-	} else {
-		nodes := []*html.Node{}
-		for c := node.FirstChild; c != nil; c = c.NextSibling {
-			nodes = append(nodes, findNodes(c, selector)...)
-		}
-		return nodes
-	}
-}
-
-func (z ZtmPoznan) GetLocation() *time.Location {
-	l, _ := time.LoadLocation("Europe/Warsaw")
-	return l
-}
-
-func (z ZtmPoznan) String() string {
-	return "poznan_ztm"
-}

+ 0 - 0
traffic/structs/access.go


Some files were not shown because too many files changed in this diff