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