penguin/golifehk

@golifehk bot in Telegram

commit 8bd60a58bbaf20f576e48bdb1d8e6323dfd31278

author斟酌 鵬兄 <tgckpg@gmail.com>
date2026-03-01T11:22:15Z
subjectValidate data before commit
commit 8bd60a58bbaf20f576e48bdb1d8e6323dfd31278
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date:   2026-03-01T11:22:15Z

    Validate data before commit
---
 datasources/kmb/query.go      |  90 +++++------
 datasources/kmb/routestops.go |  24 ++-
 utils/cache.go                | 342 +++++++++++++++++++++++-------------------
 3 files changed, 251 insertions(+), 205 deletions(-)

diff --git a/datasources/kmb/query.go b/datasources/kmb/query.go
index 5335339..2ab81c0 100644
--- a/datasources/kmb/query.go
+++ b/datasources/kmb/query.go
@@ -1,49 +1,53 @@
 package kmb
 
 import (
-    "strings"
-    query "github.com/tgckpg/golifehk/query"
+	query "github.com/tgckpg/golifehk/query"
+	"log"
+	"strings"
 )
 
-func Query( lang string, message string ) query.IQueryResult {
-
-    var qo *query.QueryObject
-    var err error
-
-    qr := QueryResult{ Lang: lang }
-    routeStops, err := getRouteStops()
-    if err != nil {
-        qr.Error = err
-        goto qrReturn
-    }
-
-    qo, err = query.Parse( strings.ToUpper( message ), routeStops )
-    if err != nil {
-        qr.Error = err
-        goto qrReturn
-    }
-
-    qr.Query = qo
-
-    if 0 < len( *qo.Results ) && 1 < len( *qo.SearchTerms ) {
-
-        rSchedules := map[*RouteStop] *[] *Schedule{}
-        for _, item := range *qo.Results {
-            var r *RouteStop
-            r = any( item ).( *RouteStop )
-            schedules, err := getSchedule( r )
-            if err != nil {
-                qr.Error = err
-                break
-            }
-
-            rSchedules[r] = schedules
-        }
-        qr.Schedules = &rSchedules
-    }
-
-    qrReturn:
-    var iqr query.IQueryResult
-    iqr = &qr
-    return iqr
+func Query(lang string, message string) query.IQueryResult {
+
+	var qo *query.QueryObject
+	var err error
+
+	qr := QueryResult{Lang: lang}
+	routeStops, err := getRouteStops()
+	if err != nil {
+		qr.Error = err
+		goto qrReturn
+	}
+
+	qo, err = query.Parse(strings.ToUpper(message), routeStops)
+	if err != nil {
+		qr.Error = err
+		goto qrReturn
+	}
+
+	qr.Query = qo
+
+	if 0 < len(*qo.Results) && 1 < len(*qo.SearchTerms) {
+
+		rSchedules := map[*RouteStop]*[]*Schedule{}
+		for _, item := range *qo.Results {
+			var r *RouteStop
+			r = any(item).(*RouteStop)
+			schedules, err := getSchedule(r)
+			if err != nil {
+				qr.Error = err
+				break
+			}
+
+			rSchedules[r] = schedules
+		}
+		qr.Schedules = &rSchedules
+	}
+
+qrReturn:
+	if qr.Error != nil {
+		log.Println(qr.Error)
+	}
+	var iqr query.IQueryResult
+	iqr = &qr
+	return iqr
 }
diff --git a/datasources/kmb/routestops.go b/datasources/kmb/routestops.go
index b5bb91d..2bfb63a 100644
--- a/datasources/kmb/routestops.go
+++ b/datasources/kmb/routestops.go
@@ -5,7 +5,6 @@ import (
 	"encoding/json"
 	"fmt"
 	"io"
-	"log"
 	"path/filepath"
 
 	query "github.com/tgckpg/golifehk/query"
@@ -99,33 +98,46 @@ func readBusStopsData(buff *bytes.Buffer) (*map[string]*BusStop, error) {
 }
 
 func getRouteStops() (*[]query.ISearchable, error) {
+	var busStopMap *map[string]*BusStop
 
 	QUERY_FUNC := func() (io.ReadCloser, error) {
 		return utils.HttpGet("https://data.etabus.gov.hk/v1/transport/kmb/stop")
 	}
 
-	buff, err := utils.CacheStream(JSON_BUSSTOPS, QUERY_FUNC, 7*24*3600)
+	PARSE_FUNC := func(buff *bytes.Buffer) error {
+		var err error
+		busStopMap, err = readBusStopsData(buff)
+		return err
+	}
+
+	cs, err := utils.CacheStreamEx(JSON_BUSSTOPS, QUERY_FUNC)
 	if err != nil {
 		return nil, err
 	}
 
-	busStopMap, err := readBusStopsData(buff)
+	err = cs.Try(PARSE_FUNC, 7*24*3600, 3)
 	if err != nil {
 		return nil, err
 	}
 
+	var routeStops *[]*RouteStop
 	QUERY_FUNC = func() (io.ReadCloser, error) {
 		return utils.HttpGet("https://data.etabus.gov.hk/v1/transport/kmb/route-stop")
 	}
 
-	buff, err = utils.CacheStream(JSON_ROUTESTOPS, QUERY_FUNC, 7*24*3600)
+	PARSE_FUNC = func(buff *bytes.Buffer) error {
+		var err error
+		routeStops, err = readRouteStopsData(busStopMap, buff)
+		return err
+	}
+
+	cs, err = utils.CacheStreamEx(JSON_ROUTESTOPS, QUERY_FUNC)
 	if err != nil {
 		return nil, err
 	}
 
-	routeStops, err := readRouteStopsData(busStopMap, buff)
+	err = cs.Try(PARSE_FUNC, 7*24*3600, 3)
 	if err != nil {
-		log.Printf("Unable to parse RouteStopsData: %s", err)
 		return nil, err
 	}
 
diff --git a/utils/cache.go b/utils/cache.go
index e2131ca..c0ecbcd 100644
--- a/utils/cache.go
+++ b/utils/cache.go
@@ -1,188 +1,218 @@
 package utils
 
 import (
-    "bytes"
-    "io"
-    "log"
-    "os"
-    "path/filepath"
-    "time"
+	"bytes"
+	"fmt"
+	"io"
+	"log"
+	"os"
+	"path/filepath"
+	"time"
 )
 
 type CacheStreams struct {
-    Local *bytes.Buffer
-    Remote *bytes.Buffer
-    Path string
-    Query func() ( io.ReadCloser, error )
+	Local  *bytes.Buffer
+	Remote *bytes.Buffer
+	Path   string
+	Query  func() (io.ReadCloser, error)
 }
 
-func ( cs *CacheStreams ) Commit() error {
+func (cs *CacheStreams) Commit() error {
 
-    f, err := os.OpenFile( cs.Path, os.O_CREATE | os.O_WRONLY | os.O_TRUNC, 0644 )
-    if err != nil {
-        return err
-    }
+	f, err := os.OpenFile(cs.Path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
+	if err != nil {
+		return err
+	}
 
-    defer f.Close()
+	defer f.Close()
 
-    _, err = io.Copy( f, bytes.NewReader( cs.Remote.Bytes() ) )
-    log.Printf( "Commit: %s", cs.Path )
-    return err
+	_, err = io.Copy(f, bytes.NewReader(cs.Remote.Bytes()))
+	log.Printf("Commit: %s", cs.Path)
+	return err
 }
 
-func ( cs *CacheStreams ) Reload() error {
+func (cs *CacheStreams) Try(Parser func(*bytes.Buffer) error, expires time.Duration, retries int) error {
+	var err error
+	for i := 0; i < retries; i++ {
+		if cs.NotExpired(expires) {
+			err = Parser(cs.Local)
+			if err == nil {
+				log.Printf("Using cache: %s", cs.Path)
+				return nil
+			}
+		}
+
+		err = cs.Reload()
+		log.Printf("Reloading (%d): %s", i+1, cs.Path)
+		if err != nil {
+			log.Printf("Error retrieving data(%s): %s", i+1, err)
+		}
+
+		if cs.Remote != nil {
+			err = Parser(cs.Remote)
+			if err == nil {
+				cs.Commit()
+				return nil
+			}
+		}
+	}
+
+	return fmt.Errorf("Failed to get data for: %s", cs.Path)
+}
+
+func (cs *CacheStreams) Reload() error {
 
-    s, err := cs.Query()
-    if err != nil {
-        return err
-    }
+	s, err := cs.Query()
+	if err != nil {
+		return err
+	}
 
-    defer s.Close()
+	defer s.Close()
 
-    cs.Remote = bytes.NewBuffer( []byte{} )
-    _, err = io.Copy( cs.Remote, s )
+	cs.Remote = bytes.NewBuffer([]byte{})
+	_, err = io.Copy(cs.Remote, s)
 
-    return err
+	return err
 }
 
-func ( cs *CacheStreams ) NotExpired( expires time.Duration ) bool {
+func (cs *CacheStreams) NotExpired(expires time.Duration) bool {
 
-    cache, err := os.Stat( cs.Path )
+	cache, err := os.Stat(cs.Path)
 
-    if err == nil {
-        expired := cache.ModTime().Add( expires * 1e9 )
-        return time.Now().Before( expired )
-    }
+	if err == nil {
+		expired := cache.ModTime().Add(expires * 1e9)
+		return time.Now().Before(expired)
+	}
 
-    return false
+	return false
 }
 
-func CacheStreamEx( path string, readStream func() ( io.ReadCloser, error ) ) ( *CacheStreams, error ) {
+func CacheStreamEx(path string, readStream func() (io.ReadCloser, error)) (*CacheStreams, error) {
 
-    cs := CacheStreams{}
-    cs.Path = path
-    cs.Query = readStream
+	cs := CacheStreams{}
+	cs.Path = path
+	cs.Query = readStream
 
-    f, err := os.Open( path )
-    if err == nil {
-        defer f.Close()
-        cs.Local = bytes.NewBuffer( []byte{} )
-        _, err = io.Copy( cs.Local, f )
-        if err != nil {
-            return nil, err
-        }
-    }
+	f, err := os.Open(path)
+	if err == nil {
+		defer f.Close()
+		cs.Local = bytes.NewBuffer([]byte{})
+		_, err = io.Copy(cs.Local, f)
+		if err != nil {
+			return nil, err
+		}
+	}
 
-    return &cs, nil
+	return &cs, nil
 }
 
-
-func CacheStream( path string, readStream func() ( io.ReadCloser, error ), expires time.Duration ) ( *bytes.Buffer, error ) {
-
-    cache, err := os.Stat( path )
-
-    // Check if cache exists and not expired
-    if err == nil {
-        expired := cache.ModTime().Add( expires * 1e9 )
-        if time.Now().Before( expired ) {
-            f, err := os.Open( path )
-            if err == nil {
-                defer f.Close()
-                log.Printf( "Using cache: %s", path )
-                writeBuff := bytes.NewBuffer( []byte{} )
-                _, err = io.Copy( writeBuff, f )
-                if err == nil {
-                    return writeBuff, nil
-                }
-            }
-        }
-    }
-
-    err = os.MkdirAll( filepath.Dir( path ), 0750 )
-    if err != nil {
-        return nil, err }
-
-    writeBuff := bytes.NewBuffer( []byte{} )
-
-    // Get the reader that return new data
-    s, err := readStream()
-    if err != nil {
-        return nil, err
-    }
-
-    defer s.Close()
-
-    _, err = io.Copy( writeBuff, s )
-    if err != nil {
-        return nil, err
-    }
-
-    f, err := os.OpenFile( path, os.O_CREATE | os.O_WRONLY | os.O_TRUNC, 0644 )
-    if err != nil {
-        return nil, err
-    }
-
-    defer f.Close()
-
-    data := writeBuff.Bytes()
-    _, err = io.Copy( f, bytes.NewReader( data ) )
-    if err != nil {
-        return nil, err
-    }
-
-    return writeBuff, nil
+func CacheStream(path string, readStream func() (io.ReadCloser, error), expires time.Duration) (*bytes.Buffer, error) {
+
+	cache, err := os.Stat(path)
+
+	// Check if cache exists and not expired
+	if err == nil {
+		expired := cache.ModTime().Add(expires * 1e9)
+		if time.Now().Before(expired) {
+			f, err := os.Open(path)
+			if err == nil {
+				defer f.Close()
+				log.Printf("Using cache: %s", path)
+				writeBuff := bytes.NewBuffer([]byte{})
+				_, err = io.Copy(writeBuff, f)
+				if err == nil {
+					return writeBuff, nil
+				}
+			}
+		}
+	}
+
+	err = os.MkdirAll(filepath.Dir(path), 0750)
+	if err != nil {
+		return nil, err
+	}
+
+	writeBuff := bytes.NewBuffer([]byte{})
+
+	// Get the reader that return new data
+	s, err := readStream()
+	if err != nil {
+		return nil, err
+	}
+
+	defer s.Close()
+
+	_, err = io.Copy(writeBuff, s)
+	if err != nil {
+		return nil, err
+	}
+
+	f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
+	if err != nil {
+		return nil, err
+	}
+
+	defer f.Close()
+
+	data := writeBuff.Bytes()
+	_, err = io.Copy(f, bytes.NewReader(data))
+	if err != nil {
+		return nil, err
+	}
+
+	return writeBuff, nil
 }
 
-func ChangedStream( path string, readStream func() ( io.Reader, error ), dataModTime time.Time ) ( *bytes.Buffer, error ) {
-
-    cache, err := os.Stat( path )
-
-    // Check if cache exists and not expired
-    if err == nil {
-        if dataModTime.Before( cache.ModTime() ) {
-            f, err := os.Open( path )
-            if err == nil {
-                defer f.Close()
-                log.Printf( "Reading from file: %s", path )
-                writeBuff := bytes.NewBuffer( []byte{} )
-                _, err = io.Copy( writeBuff, f )
-                if err == nil {
-                    return writeBuff, nil
-                }
-            }
-        }
-    }
-
-    err = os.MkdirAll( filepath.Dir( path ), 0750 )
-    if err != nil {
-        return nil, err
-    }
-
-    writeBuff := bytes.NewBuffer( []byte{} )
-
-    // Get the reader that return new data
-    s, err := readStream()
-    if err != nil {
-        return nil, err
-    }
-
-    _, err = io.Copy( writeBuff, s )
-    if err != nil {
-        return nil, err
-    }
-
-    f, err := os.OpenFile( path, os.O_CREATE | os.O_WRONLY | os.O_TRUNC, 0644 )
-    if err != nil {
-        return nil, err
-    }
-
-    defer f.Close()
-
-    data := writeBuff.Bytes()
-    _, err = io.Copy( f, bytes.NewReader( data ) )
-    if err != nil {
-        return nil, err
-    }
-
-    return writeBuff, nil
+func ChangedStream(path string, readStream func() (io.Reader, error), dataModTime time.Time) (*bytes.Buffer, error) {
+
+	cache, err := os.Stat(path)
+
+	// Check if cache exists and not expired
+	if err == nil {
+		if dataModTime.Before(cache.ModTime()) {
+			f, err := os.Open(path)
+			if err == nil {
+				defer f.Close()
+				log.Printf("Reading from file: %s", path)
+				writeBuff := bytes.NewBuffer([]byte{})
+				_, err = io.Copy(writeBuff, f)
+				if err == nil {
+					return writeBuff, nil
+				}
+			}
+		}
+	}
+
+	err = os.MkdirAll(filepath.Dir(path), 0750)
+	if err != nil {
+		return nil, err
+	}
+
+	writeBuff := bytes.NewBuffer([]byte{})
+
+	// Get the reader that return new data
+	s, err := readStream()
+	if err != nil {
+		return nil, err
+	}
+
+	_, err = io.Copy(writeBuff, s)
+	if err != nil {
+		return nil, err
+	}
+
+	f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
+	if err != nil {
+		return nil, err
+	}
+
+	defer f.Close()
+
+	data := writeBuff.Bytes()
+	_, err = io.Copy(f, bytes.NewReader(data))
+	if err != nil {
+		return nil, err
+	}
+
+	return writeBuff, nil
 }