13 Commits 3285e2b3d5 ... 99536e0a50

Author SHA1 Message Date
  Adam 99536e0a50 handle errors when getting departures 2 months ago
  Adam a83143c3fc send departures response in Bare 2 months ago
  Adam 537c0a1253 handle errors in HTTP api 2 months ago
  Adam a56324f6fd DRY when converting Version from/to version string 2 months ago
  Adam 4b165977be handle errors in initialising TRAFFIC 2 months ago
  Adam 92a8eb5ff2 handle errors when converting to TRAFFIC file 2 months ago
  Adam 9a352dba86 handle error in config reading 2 months ago
  Adam 3285e2b3d5 send departures response in Bare 2 months ago
  Adam 568fd84539 handle errors in HTTP api 2 months ago
  Adam a404e3f99c DRY when converting Version from/to version string 2 months ago
  Adam 6485862ff0 handle errors in initialising TRAFFIC 2 months ago
  Adam b95cebfed2 handle errors when converting to TRAFFIC file 2 months ago
  Adam 5258bb1d34 handle error in config reading 2 months ago
5 changed files with 331 additions and 141 deletions
  1. 3 8
      config/config.go
  2. 23 12
      server/router.go
  3. 272 120
      traffic/access.go
  4. 23 0
      traffic/errors/errors.go
  5. 10 1
      traffic/structs/structs.go

+ 3 - 8
config/config.go

@@ -56,14 +56,10 @@ func scan(input ...interface{}) interface{} {
 		}
 	}
 	args.config = config
-	return gott.Tuple{args}
-}
 
-func closeFile(input ...interface{}) (interface{}, error) {
-	args := input[0].(result)
-	err := input[1].(error)
-	args.configFile.Close()
-	return gott.Tuple{args}, err
+	configFile.Close()
+	
+	return gott.Tuple{args}
 }
 
 func Read(configPath string) (Config, error) {
@@ -74,7 +70,6 @@ func Read(configPath string) (Config, error) {
 	output, err := gott.NewResult(input).
 		Bind(openFile).
 		Map(scan).
-		Recover(closeFile).
 		Finish()
 	if err != nil {
 		return Config{}, err

+ 23 - 12
server/router.go

@@ -4,6 +4,7 @@ import (
 	api_structs "notabug.org/apiote/bimba_server/api/structs"
 	"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"
 
@@ -36,29 +37,29 @@ func handleDepartures(w http.ResponseWriter, r *http.Request, feedName string, c
 		sendError(w, r, "code", "EMPTY", 400)
 		return
 	}
-	date := r.Form.Get("date")
+	dateString := r.Form.Get("date")
 	// todo line := r.Form.Get("line")
 	// todo limit := r.Form.Get("limit")
 	limit := 12
 	versionCode := traffic_structs.Validity("")
-	departuresType := "full"
-	if date == "" {
+	departuresType := traffic_structs.DEPARTURES_FULL
+	if dateString == "" {
 		feedNow := time.Now().In(cfg.EnabledFeeds[feedName].GetLocation())
-		date = feedNow.Format("20060102")
-		departuresType = "hybrid"
+		dateString = feedNow.Format(traffic_structs.DateFormat)
+		departuresType = traffic_structs.DEPARTURES_HYBRID
 	}
-	feedTime, err := time.ParseInLocation("20060102", date, cfg.EnabledFeeds[feedName].GetLocation())
+	date, err := time.ParseInLocation(traffic_structs.DateFormat, dateString, cfg.EnabledFeeds[feedName].GetLocation())
 	if err != nil {
-		sendError(w, r, "date", date, 400)
+		sendError(w, r, "date", dateString, 400)
 	}
 	for _, v := range versions {
-		if !v.ValidFrom.After(feedTime) && !feedTime.After(v.ValidTill) {
+		if !v.ValidFrom.After(date) && !date.After(v.ValidTill) {
 			versionCode = traffic_structs.Validity(v.String())
 		}
 	}
 
 	if versionCode == "" {
-		sendError(w, r, "date", date, 404)
+		sendError(w, r, "date", dateString, 404)
 		return
 	}
 
@@ -71,19 +72,29 @@ func handleDepartures(w http.ResponseWriter, r *http.Request, feedName string, c
 		return
 	}
 
-	departures := traffic.GetDepartures(code, cfg.FeedsPath, feedName, versionCode, ix, cal, date, departuresType)
+	departures, err := traffic.GetDepartures(code, cfg.FeedsPath, feedName, versionCode, ix, cal, date, departuresType)
+	if err != nil {
+		if _, ok := err.(traffic_errors.NoSchedule); ok {
+			sendError(w, r, "date", dateString, 404)
+		} else {
+			send500(w, r, "while getting departures")
+			log.Println(err)
+			return
+		}
+	}
 	limitHere := limit
 	if len(departures) < limit {
 		limitHere = len(departures)
 	}
 	departures = departures[:limitHere]
-	response := api_structs.SuccessDepartures {
-		Success: true,
+	response := api_structs.SuccessDepartures{
+		Success:    true,
 		Departures: departures,
 	}
 	bytes, err := bare.Marshal(&response)
 	if err != nil {
 		send500(w, r, "while marshaling departures")
+		log.Println(err)
 		return
 	}
 	w.Write(bytes)

+ 272 - 120
traffic/access.go

@@ -3,6 +3,7 @@ package traffic
 import (
 	apiStructs "notabug.org/apiote/bimba_server/api/structs"
 	"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"
@@ -14,40 +15,52 @@ import (
 	"time"
 
 	"git.sr.ht/~sircmpwn/go-bare"
+	"notabug.org/apiote/gott"
 )
 
+type timedStopOrder struct {
+	Order      int
+	TripOffset uint
+	dayOffset  int
+}
+
 var lastUpdatedGtfsRt uint64 = 0
 
-func findSchedule(home, date string, calendar []traffic_structs.Schedule) string {
-	t, _ := time.Parse("2006-01-02", date)
-	weekday := uint8(1 << t.Weekday())
+func findSchedule(home string, time time.Time, calendar []traffic_structs.Schedule) (string, error) {
+	weekday := uint8(1 << time.Weekday())
+	date := time.Format(traffic_structs.DateFormat)
 	for _, schedule := range calendar {
 		if schedule.StartDate <= date && date <= schedule.EndDate && (schedule.Weekdays&weekday != 0) {
-			return schedule.ScheduleID
+			return schedule.ScheduleID, nil
 		}
 	}
-	return "" // todo return error
+	return "", traffic_errors.NoSchedule{Date: date}
 }
 
-func getRealtimeOffset(tripID string, stepSequence int) gtfs_rt.Update {
+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")
 	if err != nil {
-		log.Println("cannot get GTFS-RT feeds")
+		return gtfs_rt.Update{}, err
 	}
 	lastUpdatedGtfsRt = lastUpdated
 	update := updates[tripID]
-	return update
+	return update, nil
 }
 
-func calculateGtfsTime(gtfsTime uint, delay int32, date string, timezone *time.Location) time.Time {
-	t, _ := time.ParseInLocation("2006-01-02T15:04", date+"T12:00", timezone)
+func calculateGtfsTime(gtfsTime uint, delay int32, date time.Time, timezone *time.Location) (time.Time, error) {
+	noon := time.Date(date.Year(), date.Month(), date.Day(), 12, 0, 0, 0, timezone)
 	twelve, _ := time.ParseDuration("-12h")
-	t = t.Add(twelve)
-	duration, _ := time.ParseDuration(strconv.FormatInt(int64(gtfsTime), 10) + "m")
-	delayDuration, _ := time.ParseDuration(strconv.FormatInt(int64(delay), 10) + "s")
-	t = t.Add(duration)
-	t = t.Add(delayDuration)
-	return t
+	midnight := noon.Add(twelve)
+	departureDuration, err := time.ParseDuration(strconv.FormatInt(int64(gtfsTime), 10) + "m")
+	if err != nil {
+		return midnight, err
+	}
+	delayDuration, err := time.ParseDuration(strconv.FormatInt(int64(delay), 10) + "s")
+	if err != nil {
+		return midnight, err
+	}
+	t := midnight.Add(departureDuration).Add(delayDuration)
+	return t, nil
 }
 
 func getDayOffset(t1, t2 time.Time) int8 {
@@ -63,133 +76,210 @@ func getDayOffset(t1, t2 time.Time) int8 {
 	}
 }
 
-func marshalStopOrder(tripOffset uint, stopOrder int) string {
+func marshalStopOrder(tripOffset uint, stopOrder int) (string, error) {
 	order := traffic_structs.StopOrder{
 		TripOffset: tripOffset,
 		Order:      stopOrder,
 	}
-	bytes, _ := bare.Marshal(&order)
-	return hex.EncodeToString(bytes)
+	bytes, err := bare.Marshal(&order)
+	return hex.EncodeToString(bytes), err
+}
+
+type getDeparturesResult struct {
+	StopOffset     uint
+	Date           time.Time
+	TimetableHome  string
+	Calendar       []traffic_structs.Schedule
+	DeparturesType traffic_structs.DeparturesType
+
+	Location          *time.Location
+	Datetime          time.Time
+	MinuteB4Datetime  time.Time
+	Midnight          time.Time
+	TodaySchedule     string
+	YesterdaySchedule string
+	StopsFile         *os.File
+	Stop              traffic_structs.Stop
+	TripsFile         *os.File
+	StopOrders        []timedStopOrder
+	Trips             map[uint]traffic_structs.Trip
+	DeparturesFile    *os.File
+
+	Departures []apiStructs.Departure
+}
+
+func loadLocation(input ...interface{}) (interface{}, error) {
+	result := input[0].(getDeparturesResult)
+	location, err := time.LoadLocation("Europe/Warsaw") // todo stopLocation ?: feedLocation
+	result.Location = location
+	return result, err
 }
 
-func GetDepartures(stopCode traffic_structs.ID, dataHome, feedName string, versionCode traffic_structs.Validity, codeIndex traffic_structs.CodeIndex, calendar []traffic_structs.Schedule, date string, departuresType string) []apiStructs.Departure {
-	stopCodePresent := codeIndex[stopCode]
+func loadTime(input ...interface{}) interface{} {
+	result := input[0].(getDeparturesResult)
 
 	deadzone, _ := time.ParseDuration("-1m")
-	location, _ := time.LoadLocation("Europe/Warsaw") // todo stopLocation ?: feedLocation
-	thisDay, _ := time.Parse("20060102", date)
 	now := time.Now()
-	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, string(versionCode))
-
-	todaySchedule := findSchedule(timetableHome, today, calendar)
-	yesterdaySchedule := findSchedule(timetableHome, yesterday, calendar)
-
-	stopsFile, _ := os.Open(filepath.Join(timetableHome, "stops.bare"))
-	defer stopsFile.Close()
-	stopsFile.Seek(int64(stopCodePresent.Offset), 0)
-	stop := traffic_structs.Stop{}
-	bare.UnmarshalReader(stopsFile, &stop)
-
-	tripsFile, _ := os.Open(filepath.Join(timetableHome, "trips.bare"))
-	defer tripsFile.Close()
+	datetime := time.Date(result.Date.Year(), result.Date.Month(),
+		result.Date.Day(), now.Hour(), now.Minute(), now.Second(), 0,
+		result.Location)
+	result.Datetime = datetime
+	result.MinuteB4Datetime = datetime.Add(deadzone)
+	result.Midnight = time.Date(datetime.Year(), datetime.Month(),
+		datetime.Day(), 0, 0, 0, 0, result.Location)
+	return result
+}
+
+func loadTodaySchedule(input ...interface{}) (interface{}, error) {
+	result := input[0].(getDeparturesResult)
+
+	yesterday := result.Date.AddDate(0, 0, -1)
+	yesterdaySchedule, err := findSchedule(result.TimetableHome, yesterday,
+		result.Calendar)
+	result.YesterdaySchedule = yesterdaySchedule
+	return result, err
+}
+
+func loadYesterdaySchedule(input ...interface{}) (interface{}, error) {
+	result := input[0].(getDeparturesResult)
+
+	todaySchedule, err := findSchedule(result.TimetableHome, result.Date,
+		result.Calendar)
+	result.TodaySchedule = todaySchedule
+	return result, err
+}
+
+func openStopsFile(input ...interface{}) (interface{}, error) {
+	result := input[0].(getDeparturesResult)
+
+	stopsFile, err := os.Open(filepath.Join(result.TimetableHome, "stops.bare"))
+	result.StopsFile = stopsFile
+	return result, err
+}
+
+func seekStops(input ...interface{}) (interface{}, error) {
+	result := input[0].(getDeparturesResult)
+
+	_, err := result.StopsFile.Seek(int64(result.StopOffset), 0)
+	return result, err
+}
+
+func unmarshalStop(input ...interface{}) (interface{}, error) {
+	result := input[0].(getDeparturesResult)
+
+	result.Stop = traffic_structs.Stop{}
+	err := bare.UnmarshalReader(result.StopsFile, &result.Stop)
+	result.StopsFile.Close()
+	return result, err
+}
+
+func openTripsFile(input ...interface{}) (interface{}, error) {
+	result := input[0].(getDeparturesResult)
+
+	tripsFile, err := os.Open(filepath.Join(result.TimetableHome, "trips.bare"))
+	result.TripsFile = tripsFile
+	return result, err
+}
+
+func readStopOrders(input ...interface{}) (interface{}, error) {
+	result := input[0].(getDeparturesResult)
+
 	trips := map[uint]traffic_structs.Trip{}
-	stopOrders := []traffic_structs.StopOrder{}
-	yStopOrders := []traffic_structs.StopOrder{}
-	for _, order := range stop.Order {
-		tripsFile.Seek(int64(order.TripOffset), 0)
+	stopOrders := []timedStopOrder{}
+	for _, order := range result.Stop.Order {
+		_, err := result.TripsFile.Seek(int64(order.TripOffset), 0)
+		if err != nil {
+			return result, err
+		}
 		trip := traffic_structs.Trip{}
-		bare.UnmarshalReader(tripsFile, &trip)
-		if trip.ScheduleID == todaySchedule {
+		err = bare.UnmarshalReader(result.TripsFile, &trip)
+		if err != nil {
+			return result, err
+		}
+		if trip.ScheduleID == result.TodaySchedule {
 			trips[order.TripOffset] = trip
-			stopOrder := traffic_structs.StopOrder{
+			stopOrder := timedStopOrder{
+				dayOffset:  0,
 				TripOffset: order.TripOffset,
 				Order:      order.Order,
 			}
 			stopOrders = append(stopOrders, stopOrder)
-		} else if trip.ScheduleID == yesterdaySchedule {
+		} else if trip.ScheduleID == result.YesterdaySchedule {
 			trips[order.TripOffset] = trip
-			stopOrder := traffic_structs.StopOrder{
+			stopOrder := timedStopOrder{
+				dayOffset:  -1,
 				TripOffset: order.TripOffset,
 				Order:      order.Order,
 			}
-			yStopOrders = append(yStopOrders, stopOrder)
+			stopOrders = append(stopOrders, stopOrder)
 		}
 	}
+	result.StopOrders = stopOrders
+	result.Trips = trips
+	result.TripsFile.Close()
+	return result, nil
+}
+
+func openDeparturesFile(input ...interface{}) (interface{}, error) {
+	result := input[0].(getDeparturesResult)
 
-	departuresFile, _ := os.Open(filepath.Join(timetableHome, "departures.bare"))
-	defer departuresFile.Close()
+	departuresFile, err := os.Open(filepath.Join(result.TimetableHome,
+		"departures.bare"))
+	result.DeparturesFile = departuresFile
+	return result, err
+}
+
+func readDepartures(input ...interface{}) (interface{}, error) {
+	result := input[0].(getDeparturesResult)
 
 	departures := []apiStructs.Departure{}
-	for _, order := range stopOrders {
-		trip := trips[order.TripOffset]
-		departuresFile.Seek(int64(trip.DeparturesOffset), 0)
+	for _, order := range result.StopOrders {
+		date := result.Date.AddDate(0, 0, order.dayOffset)
+		trip := result.Trips[order.TripOffset]
+		_, err := result.DeparturesFile.Seek(int64(trip.DeparturesOffset), 0)
+		if err != nil {
+			return result, err
+		}
 		departureRaw := traffic_structs.Departure{}
-		bare.UnmarshalReader(departuresFile, &departureRaw)
+		err = bare.UnmarshalReader(result.DeparturesFile, &departureRaw)
+		if err != nil {
+			return nil, err
+		}
 		tripID := departureRaw.TripID
 		for departureRaw.StopSeq != order.Order && departureRaw.TripID == tripID {
-			bare.UnmarshalReader(departuresFile, &departureRaw)
-		}
-		// todo if tripID != -> error
-		update := gtfs_rt.Update{}
-		if departuresType == "hybrid" {
-			update = getRealtimeOffset(tripID, order.Order)
-		}
-		departureTime := calculateGtfsTime(departureRaw.Time, update.Delay, today, location)
-		if departuresType == "full" || departureTime.After(minuteB4Now) {
-			status := apiStructs.VEHICLE_IN_TRANSIT
-			timeToArrival := departureTime.Sub(thisTime).Minutes()
-			if apiStructs.VehicleStatus(update.Status) == apiStructs.VEHICLE_AT_STOP {
-				status = apiStructs.VEHICLE_AT_STOP
-			} else if timeToArrival < 0 {
-				status = apiStructs.VEHICLE_DEPARTED
-			} else if (timeToArrival < 1 && timeToArrival >= 0) || (apiStructs.VehicleStatus(update.Status) == apiStructs.VEHICLE_INCOMING && update.StopSeq == uint32(order.Order)) {
-				status = apiStructs.VEHICLE_INCOMING
-			}
-			zoneAbbr, _ := departureTime.Zone()
-			departure := apiStructs.Departure{
-				StopOrder:  marshalStopOrder(order.TripOffset, order.Order),
-				Headsign:   trip.Headsign,
-				Line:       trip.LineName,
-				IsRealtime: update.TripUpdate != nil,
-				Status:     status,
-				Time: apiStructs.Time{
-					DayOffset: getDayOffset(minuteB4Now, departureTime),
-					Hour:      uint(departureTime.Hour()),
-					Minute:    uint(departureTime.Minute()),
-					Second:    uint(departureTime.Second()),
-					Zone:      zoneAbbr,
-				},
+			err = bare.UnmarshalReader(result.DeparturesFile, &departureRaw)
+			if err != nil {
+				return result, err
 			}
-			departures = append(departures, departure)
 		}
-	}
-	for _, order := range yStopOrders {
-		trip := trips[order.TripOffset]
-		departuresFile.Seek(int64(trip.DeparturesOffset), 0)
-		departureRaw := traffic_structs.Departure{}
-		bare.UnmarshalReader(departuresFile, &departureRaw)
-		tripID := departureRaw.TripID
-		for departureRaw.StopSeq != order.Order && departureRaw.TripID == tripID {
-			bare.UnmarshalReader(departuresFile, &departureRaw)
+		if tripID != departureRaw.TripID {
+			return result, traffic_errors.NoStopOrder{StopOrder: traffic_structs.StopOrder{
+				TripOffset: order.TripOffset,
+				Order:      order.Order,
+			}}
+		}
+		departureTime, err := calculateGtfsTime(departureRaw.Time, 0, date,
+			result.Location)
+		if err != nil {
+			return result, err
 		}
-		// todo if tripID != -> error
-		departureTime := calculateGtfsTime(departureRaw.Time, 0, yesterday, location)
-		if departuresType == "full" || departureTime.After(midnight) {
+		if departureTime.After(result.Midnight) {
 			update := gtfs_rt.Update{}
-			if departuresType == "hybrid" {
-				update = getRealtimeOffset(tripID, order.Order)
+			if result.DeparturesType == traffic_structs.DEPARTURES_HYBRID {
+				update, err = getRealtimeOffset(tripID, order.Order)
+				if err != nil {
+					log.Printf("while getting realtime departures: %v\n", err)
+				}
+			}
+			departureTime, err := calculateGtfsTime(departureRaw.Time, update.Delay,
+				date, result.Location)
+			if err != nil {
+				return result, err
 			}
-			departureTime := calculateGtfsTime(departureRaw.Time, update.Delay, yesterday, location)
-			if (departuresType == "full" && departureTime.After(midnight)) || (departuresType == "hybrid" && departureTime.After(minuteB4Now)) {
+			if result.DeparturesType == traffic_structs.DEPARTURES_FULL ||
+				departureTime.After(result.MinuteB4Datetime) {
 				status := apiStructs.VEHICLE_IN_TRANSIT
-				timeToArrival := departureTime.Sub(thisTime).Minutes()
+				timeToArrival := departureTime.Sub(result.Datetime).Minutes()
 				if apiStructs.VehicleStatus(update.Status) == apiStructs.VEHICLE_AT_STOP {
 					status = apiStructs.VEHICLE_AT_STOP
 				} else if timeToArrival < 0 {
@@ -198,14 +288,18 @@ func GetDepartures(stopCode traffic_structs.ID, dataHome, feedName string, versi
 					status = apiStructs.VEHICLE_INCOMING
 				}
 				zoneAbbr, _ := departureTime.Zone()
+				stopOrder, err := marshalStopOrder(order.TripOffset, order.Order)
+				if err != nil {
+					return result, err
+				}
 				departure := apiStructs.Departure{
-					StopOrder:  marshalStopOrder(order.TripOffset, order.Order),
 					Headsign:   trip.Headsign,
+					StopOrder:  stopOrder,
 					Line:       trip.LineName,
 					Status:     status,
 					IsRealtime: update.TripUpdate != nil,
 					Time: apiStructs.Time{
-						DayOffset: getDayOffset(minuteB4Now, departureTime),
+						DayOffset: getDayOffset(result.MinuteB4Datetime, departureTime),
 						Hour:      uint(departureTime.Hour()),
 						Minute:    uint(departureTime.Minute()),
 						Second:    uint(departureTime.Second()),
@@ -216,17 +310,75 @@ func GetDepartures(stopCode traffic_structs.ID, dataHome, feedName string, versi
 			}
 		}
 	}
+	result.Departures = departures
+	return result, nil
+}
+
+func sortDepartures(input ...interface{}) interface{} {
+	result := input[0].(getDeparturesResult)
 
-	sort.Slice(departures, func(i, j int) bool {
-		if departures[i].Time.DayOffset != departures[j].Time.DayOffset {
-			return departures[i].Time.DayOffset < departures[j].Time.DayOffset
-		} else if departures[i].Time.Hour != departures[j].Time.Hour {
-			return departures[i].Time.Hour < departures[j].Time.Hour
-		} else if departures[i].Time.Minute != departures[j].Time.Minute {
-			return departures[i].Time.Minute < departures[j].Time.Minute
+	sort.Slice(result.Departures, func(i, j int) bool {
+		if result.Departures[i].Time.DayOffset != result.Departures[j].Time.DayOffset {
+			return result.Departures[i].Time.DayOffset < result.Departures[j].Time.DayOffset
+		} else if result.Departures[i].Time.Hour != result.Departures[j].Time.Hour {
+			return result.Departures[i].Time.Hour < result.Departures[j].Time.Hour
+		} else if result.Departures[i].Time.Minute != result.Departures[j].Time.Minute {
+			return result.Departures[i].Time.Minute < result.Departures[j].Time.Minute
 		} else {
-			return departures[i].Time.Second < departures[j].Time.Second
+			return result.Departures[i].Time.Second < result.Departures[j].Time.Second
 		}
 	})
-	return departures
+
+	return result
+}
+
+func closeFiles(input ...interface{}) (interface{}, error) {
+	result := input[0].(getDeparturesResult)
+	err := input[1].(error)
+	if result.StopsFile != nil {
+		result.StopsFile.Close()
+	}
+	if result.TripsFile != nil {
+		result.TripsFile.Close()
+	}
+	if result.DeparturesFile != nil {
+		result.DeparturesFile.Close()
+	}
+	return result, err
+}
+
+func GetDepartures(stopCode traffic_structs.ID, dataHome, feedName string,
+	versionCode traffic_structs.Validity, codeIndex traffic_structs.CodeIndex,
+	calendar []traffic_structs.Schedule, date time.Time,
+	departuresType traffic_structs.DeparturesType) ([]apiStructs.Departure, error) {
+
+	result := getDeparturesResult{
+		StopOffset:     codeIndex[stopCode].Offset,
+		Date:           date,
+		TimetableHome:  filepath.Join(dataHome, feedName, string(versionCode)),
+		Calendar:       calendar,
+		DeparturesType: departuresType,
+	}
+
+	r, e := gott.NewResult(result).
+		Bind(loadLocation).
+		Map(loadTime).
+		Bind(loadTodaySchedule).
+		Bind(loadYesterdaySchedule).
+		Bind(openStopsFile).
+		Bind(seekStops).
+		Bind(unmarshalStop).
+		Bind(openTripsFile).
+		Bind(readStopOrders).
+		Bind(openDeparturesFile).
+		Bind(readDepartures).
+		Map(sortDepartures).
+		Recover(closeFiles).
+		Finish()
+
+	if e != nil {
+		return []apiStructs.Departure{}, e
+	} else {
+		return r.(getDeparturesResult).Departures, nil
+	}
 }

+ 23 - 0
traffic/errors/errors.go

@@ -0,0 +1,23 @@
+package errors
+
+import (
+	"fmt"
+
+	"notabug.org/apiote/bimba_server/traffic/structs"
+)
+
+type NoSchedule struct {
+	Date string
+}
+
+func (e NoSchedule) Error() string {
+	return "No schedule found for " + e.Date
+}
+
+type NoStopOrder struct {
+	StopOrder structs.StopOrder
+}
+
+func (e NoStopOrder) Error() string {
+	return fmt.Sprintf("No stopOrder for trip %d, order %d", e.StopOrder.TripOffset, e.StopOrder.Order)
+}

+ 10 - 1
traffic/structs/structs.go

@@ -1,5 +1,12 @@
 package structs
 
+type DeparturesType uint8
+
+const (
+	DEPARTURES_HYBRID DeparturesType = iota
+	DEPARTURES_FULL
+)
+
 type Vehicle struct {
 	Id           string
 	Capabilities uint8
@@ -72,7 +79,7 @@ type Validity string // 20060102_20060102
 
 type PresentCode struct {
 	Present bool
-	Offset uint
+	Offset  uint
 }
 type CodeIndex map[ID]PresentCode
 type FeedCodeIndex map[Validity]CodeIndex
@@ -80,3 +87,5 @@ type GlobalCodeIndex map[string]FeedCodeIndex
 
 type FeedCalendar map[Validity][]Schedule
 type GlobalCalendar map[string]FeedCalendar
+
+var DateFormat string = "2006-01-02"