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  }