update_geo.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. package updater
  2. import (
  3. "errors"
  4. "fmt"
  5. "os"
  6. "runtime"
  7. "time"
  8. "github.com/metacubex/mihomo/common/atomic"
  9. "github.com/metacubex/mihomo/component/geodata"
  10. _ "github.com/metacubex/mihomo/component/geodata/standard"
  11. "github.com/metacubex/mihomo/component/mmdb"
  12. C "github.com/metacubex/mihomo/constant"
  13. "github.com/metacubex/mihomo/log"
  14. "github.com/oschwald/maxminddb-golang"
  15. )
  16. var (
  17. UpdatingGeo atomic.Bool
  18. )
  19. func updateGeoDatabases() error {
  20. defer runtime.GC()
  21. geoLoader, err := geodata.GetGeoDataLoader("standard")
  22. if err != nil {
  23. return err
  24. }
  25. if C.GeodataMode {
  26. data, err := downloadForBytes(C.GeoIpUrl)
  27. if err != nil {
  28. return fmt.Errorf("can't download GeoIP database file: %w", err)
  29. }
  30. if _, err = geoLoader.LoadIPByBytes(data, "cn"); err != nil {
  31. return fmt.Errorf("invalid GeoIP database file: %s", err)
  32. }
  33. if err = saveFile(data, C.Path.GeoIP()); err != nil {
  34. return fmt.Errorf("can't save GeoIP database file: %w", err)
  35. }
  36. } else {
  37. defer mmdb.ReloadIP()
  38. data, err := downloadForBytes(C.MmdbUrl)
  39. if err != nil {
  40. return fmt.Errorf("can't download MMDB database file: %w", err)
  41. }
  42. instance, err := maxminddb.FromBytes(data)
  43. if err != nil {
  44. return fmt.Errorf("invalid MMDB database file: %s", err)
  45. }
  46. _ = instance.Close()
  47. mmdb.IPInstance().Reader.Close() // mmdb is loaded with mmap, so it needs to be closed before overwriting the file
  48. if err = saveFile(data, C.Path.MMDB()); err != nil {
  49. return fmt.Errorf("can't save MMDB database file: %w", err)
  50. }
  51. }
  52. if C.ASNEnable {
  53. defer mmdb.ReloadASN()
  54. data, err := downloadForBytes(C.ASNUrl)
  55. if err != nil {
  56. return fmt.Errorf("can't download ASN database file: %w", err)
  57. }
  58. instance, err := maxminddb.FromBytes(data)
  59. if err != nil {
  60. return fmt.Errorf("invalid ASN database file: %s", err)
  61. }
  62. _ = instance.Close()
  63. mmdb.ASNInstance().Reader.Close()
  64. if err = saveFile(data, C.Path.ASN()); err != nil {
  65. return fmt.Errorf("can't save ASN database file: %w", err)
  66. }
  67. }
  68. data, err := downloadForBytes(C.GeoSiteUrl)
  69. if err != nil {
  70. return fmt.Errorf("can't download GeoSite database file: %w", err)
  71. }
  72. if _, err = geoLoader.LoadSiteByBytes(data, "cn"); err != nil {
  73. return fmt.Errorf("invalid GeoSite database file: %s", err)
  74. }
  75. if err = saveFile(data, C.Path.GeoSite()); err != nil {
  76. return fmt.Errorf("can't save GeoSite database file: %w", err)
  77. }
  78. geodata.ClearCache()
  79. return nil
  80. }
  81. var ErrGetDatabaseUpdateSkip = errors.New("GEO database is updating, skip")
  82. func UpdateGeoDatabases() error {
  83. log.Infoln("[GEO] Start updating GEO database")
  84. if UpdatingGeo.Load() {
  85. return ErrGetDatabaseUpdateSkip
  86. }
  87. UpdatingGeo.Store(true)
  88. defer UpdatingGeo.Store(false)
  89. log.Infoln("[GEO] Updating GEO database")
  90. if err := updateGeoDatabases(); err != nil {
  91. log.Errorln("[GEO] update GEO database error: %s", err.Error())
  92. return err
  93. }
  94. return nil
  95. }
  96. func getUpdateTime() (err error, time time.Time) {
  97. var fileInfo os.FileInfo
  98. if C.GeodataMode {
  99. fileInfo, err = os.Stat(C.Path.GeoIP())
  100. if err != nil {
  101. return err, time
  102. }
  103. } else {
  104. fileInfo, err = os.Stat(C.Path.MMDB())
  105. if err != nil {
  106. return err, time
  107. }
  108. }
  109. return nil, fileInfo.ModTime()
  110. }
  111. func RegisterGeoUpdater(onSuccess func()) {
  112. if C.GeoUpdateInterval <= 0 {
  113. log.Errorln("[GEO] Invalid update interval: %d", C.GeoUpdateInterval)
  114. return
  115. }
  116. go func() {
  117. ticker := time.NewTicker(time.Duration(C.GeoUpdateInterval) * time.Hour)
  118. defer ticker.Stop()
  119. err, lastUpdate := getUpdateTime()
  120. if err != nil {
  121. log.Errorln("[GEO] Get GEO database update time error: %s", err.Error())
  122. return
  123. }
  124. log.Infoln("[GEO] last update time %s", lastUpdate)
  125. if lastUpdate.Add(time.Duration(C.GeoUpdateInterval) * time.Hour).Before(time.Now()) {
  126. log.Infoln("[GEO] Database has not been updated for %v, update now", time.Duration(C.GeoUpdateInterval)*time.Hour)
  127. if err := UpdateGeoDatabases(); err != nil {
  128. log.Errorln("[GEO] Failed to update GEO database: %s", err.Error())
  129. return
  130. } else {
  131. onSuccess()
  132. }
  133. }
  134. for range ticker.C {
  135. log.Infoln("[GEO] updating database every %d hours", C.GeoUpdateInterval)
  136. if err := UpdateGeoDatabases(); err != nil {
  137. log.Errorln("[GEO] Failed to update GEO database: %s", err.Error())
  138. } else {
  139. onSuccess()
  140. }
  141. }
  142. }()
  143. }