diff --git a/main.go b/main.go index 10e689b..58d2142 100644 --- a/main.go +++ b/main.go @@ -1,53 +1,53 @@ //----------------------------- -//Distributed under GPL -//Author Sergey Kalinin -//svk@nuk-svk.ru +// Distributed under GPL +// Author Sergey Kalinin +// svk@nuk-svk.ru //------------------------------ package main import ( - "crypto/sha256" - "encoding/hex" "encoding/json" - "fmt" "html/template" - "io" "log" "net" "net/http" - "os" - "path/filepath" - "strings" - "sync" - "time" + "strings" + "os" + "io" + "path/filepath" + "fmt" + "time" + "sync" + "encoding/hex" + "crypto/sha256" - "github.com/go-co-op/gocron" "github.com/gorilla/mux" - "github.com/likexian/whois" "github.com/oschwald/geoip2-golang" "golang.org/x/text/language" + "github.com/likexian/whois" + "github.com/go-co-op/gocron" ) type GeoData struct { - // IP адрес + // IP адрес IPAddress string `json:"ip_address"` // Местоположение Location struct { - Country string `json:"country,omitempty"` - City string `json:"city,omitempty"` - Continent string `json:"continent,omitempty"` - Region string `json:"region,omitempty"` - RegionISO string `json:"region_iso,omitempty"` - Latitude float64 `json:"latitude,omitempty"` - Longitude float64 `json:"longitude,omitempty"` - TimeZone string `json:"time_zone,omitempty"` - PostalCode string `json:"postal_code,omitempty"` + Country string `json:"country,omitempty"` + City string `json:"city,omitempty"` + Continent string `json:"continent,omitempty"` + Region string `json:"region,omitempty"` + RegionISO string `json:"region_iso,omitempty"` + Latitude float64 `json:"latitude,omitempty"` + Longitude float64 `json:"longitude,omitempty"` + TimeZone string `json:"time_zone,omitempty"` + PostalCode string `json:"postal_code,omitempty"` } `json:"location"` // Данные о сети Network struct { - ASN uint `json:"asn,omitempty"` - ASOrg string `json:"autonomous_system_organization,omitempty"` + ASN uint `json:"asn,omitempty"` + ASOrg string `json:"autonomous_system_organization,omitempty"` } `json:"network"` // ISO коды Codes struct { @@ -71,9 +71,9 @@ type TemplateData struct { *GeoData Error string JSONData string - WhoIsData string - BaseURL string - DBVersion string + WhoIsData string + BaseURL string + DBVersion string } type Config struct { @@ -84,166 +84,167 @@ type Config struct { } var ( - cityDB *geoip2.Reader - countryDB *geoip2.Reader - asnDB *geoip2.Reader - templates *template.Template - cfg *Config - dbMutex sync.RWMutex - dbFilesVersion string + cityDB *geoip2.Reader + countryDB *geoip2.Reader + asnDB *geoip2.Reader + templates *template.Template + cfg *Config + dbMutex sync.RWMutex + dbFilesVersion string ) + + func main() { var err error - // Загрузка конфигурации + // Загрузка конфигурации cfg = LoadConfig() - // Запускаем скачивание файлов - mmdbDownload("GeoLite2-City.mmdb") - mmdbDownload("GeoLite2-Country.mmdb") - mmdbDownload("GeoLite2-ASN.mmdb") - - // Запускаем планировщик параллельно с основным процессом - go sheduler() - - // Открываем БД - pathGeoLite2City := filepath.Join(cfg.MMDBLocalPath, "GeoLite2-City.mmdb") - log.Println(pathGeoLite2City) + // Запускаем скачивание файлов + mmdbDownload("GeoLite2-City.mmdb") + mmdbDownload("GeoLite2-Country.mmdb") + mmdbDownload("GeoLite2-ASN.mmdb") + // Запускаем планировщик параллельно с основным процессом + go sheduler() + + // Открываем БД + pathGeoLite2City := filepath.Join(cfg.MMDBLocalPath, "GeoLite2-City.mmdb") + log.Println(pathGeoLite2City) + cityDB, err = geoip2.Open(pathGeoLite2City) if err != nil { log.Fatal("Failed to open City database:", err) } defer cityDB.Close() - pathGeoLite2Country := filepath.Join(cfg.MMDBLocalPath, "GeoLite2-Country.mmdb") + pathGeoLite2Country := filepath.Join(cfg.MMDBLocalPath,"GeoLite2-Country.mmdb") countryDB, err = geoip2.Open(pathGeoLite2Country) if err != nil { log.Fatal("Failed to open Country database:", err) } defer countryDB.Close() - pathGeoLite2ASN := filepath.Join(cfg.MMDBLocalPath, "GeoLite2-ASN.mmdb") + pathGeoLite2ASN := filepath.Join(cfg.MMDBLocalPath, "GeoLite2-ASN.mmdb") asnDB, err = geoip2.Open(pathGeoLite2ASN) if err != nil { log.Fatal("Failed to open ASN database:", err) } defer asnDB.Close() - dbFilesVersion = getFileModTime(pathGeoLite2City) + dbFilesVersion = getFileModTime(pathGeoLite2City) + + // Загружаем шаблон + templates = template.Must(template.ParseFiles(filepath.Join(cfg.HTMLTemplatePath,"index.html"))) - // Загружаем шаблон - templates = template.Must(template.ParseFiles(filepath.Join(cfg.HTMLTemplatePath, "index.html"))) - - // Обработка ссылок + // Обработка ссылок r := mux.NewRouter() r.HandleFunc("/", homeHandler) r.HandleFunc("/api/{ip}", apiHandler) r.HandleFunc("/api/", apiCurrentHandler) r.HandleFunc("/lookup/{ip}", lookupHandler) r.HandleFunc("/{ip}", lookupHandler) - - log.Println("Server starting on:", cfg.ListenPort) - log.Fatal(http.ListenAndServe(":"+cfg.ListenPort, r)) + + log.Println("Server starting on:",cfg.ListenPort) + log.Fatal(http.ListenAndServe(":" + cfg.ListenPort, r)) } func getFileModTime(filePath string) string { - // Получаем информацию о файле - fileInfo, err := os.Stat(filePath) - if err != nil { - log.Println("Ошибка в getFileModTime:", err) - return "Error" - } + // Получаем информацию о файле + fileInfo, err := os.Stat(filePath) + if err != nil { + log.Println("Ошибка в getFileModTime:", err) + return "Error" + } - // Получаем время последнего изменения - modTime := fileInfo.ModTime() - // fmt.Println("Время изменения файла:", modTime) - - // Форматируем вывод - // fmt.Printf("Форматированное время: %s\n", modTime.Format("02/01/2006 15:04:05")) - return modTime.Format("02/01/2006 15:04:05") + // Получаем время последнего изменения + modTime := fileInfo.ModTime() + // fmt.Println("Время изменения файла:", modTime) + + // Форматируем вывод + // fmt.Printf("Форматированное время: %s\n", modTime.Format("02/01/2006 15:04:05")) + return modTime.Format("02/01/2006 15:04:05") } - // reopenDBs закрывает и заново открывает нужную БД func reopenDBs(fileName string) { - dbMutex.Lock() - defer dbMutex.Unlock() - - var err error - - switch fileName { - case "GeoLite2-City.mmdb": - if cityDB != nil { - cityDB.Close() - } - // Открываем базы данных заново - pathGeoLite2City := filepath.Join(cfg.MMDBLocalPath, fileName) - cityDB, err = geoip2.Open(pathGeoLite2City) - if err != nil { - log.Printf("Ошибка открытия базы данных %v: %v", fileName, err) - return - } - case "GeoLite2-Country.mmdb": - if countryDB != nil { - countryDB.Close() - } - pathGeoLite2Country := filepath.Join(cfg.MMDBLocalPath, fileName) - countryDB, err = geoip2.Open(pathGeoLite2Country) - if err != nil { - log.Printf("Ошибка открытия базы данных %v: %v", fileName, err) - return - } - case "GeoLite2-ASN.mmdb": - if asnDB != nil { - asnDB.Close() - } - pathGeoLite2ASN := filepath.Join(cfg.MMDBLocalPath, fileName) - asnDB, err = geoip2.Open(pathGeoLite2ASN) - if err != nil { - log.Printf("Ошибка открытия базы данных %v: %v", fileName, err) - return - } - } - log.Println("Открыта база данных:", fileName) - dbFilesVersion = getFileModTime(filepath.Join(cfg.MMDBLocalPath, fileName)) + dbMutex.Lock() + defer dbMutex.Unlock() + + var err error + + switch fileName { + case "GeoLite2-City.mmdb": + if cityDB != nil { + cityDB.Close() + } + // Открываем базы данных заново + pathGeoLite2City := filepath.Join(cfg.MMDBLocalPath, fileName) + cityDB, err = geoip2.Open(pathGeoLite2City) + if err != nil { + log.Printf("Ошибка открытия базы данных %v: %v", fileName, err) + return + } + case "GeoLite2-Country.mmdb": + if countryDB != nil { + countryDB.Close() + } + pathGeoLite2Country := filepath.Join(cfg.MMDBLocalPath, fileName) + countryDB, err = geoip2.Open(pathGeoLite2Country) + if err != nil { + log.Printf("Ошибка открытия базы данных %v: %v", fileName, err) + return + } + case "GeoLite2-ASN.mmdb": + if asnDB != nil { + asnDB.Close() + } + pathGeoLite2ASN := filepath.Join(cfg.MMDBLocalPath, fileName) + asnDB, err = geoip2.Open(pathGeoLite2ASN) + if err != nil { + log.Printf("Ошибка открытия базы данных %v: %v", fileName, err) + return + } + } + log.Println("Открыта база данных:", fileName) + dbFilesVersion = getFileModTime(filepath.Join(cfg.MMDBLocalPath, fileName)) } func homeHandler(w http.ResponseWriter, r *http.Request) { - baseURL := getBaseURL(r) + baseURL := getBaseURL(r) + + // Обрабатываем параметр ip из GET запроса + ipStr := r.URL.Query().Get("ip") + var geoData *GeoData + var errMsg string + var whoisData string - // Обрабатываем параметр ip из GET запроса - ipStr := r.URL.Query().Get("ip") - var geoData *GeoData - var errMsg string - var whoisData string + if ipStr != "" { + ip := net.ParseIP(ipStr) + if ip == nil { + errMsg = "Неверный IP адрес: " + ipStr + } else { + preferredLocale := getPreferredLocale(r) + var err error + geoData, err = getGeoData(ip, preferredLocale) + if err != nil { + errMsg = err.Error() + } + whoisData = getWhoIsInfo(ipStr) + } + } - if ipStr != "" { - ip := net.ParseIP(ipStr) - if ip == nil { - errMsg = "Неверный IP адрес: " + ipStr - } else { - preferredLocale := getPreferredLocale(r) - var err error - geoData, err = getGeoData(ip, preferredLocale) - if err != nil { - errMsg = err.Error() - } - whoisData = getWhoIsInfo(ipStr) - } - } + data := TemplateData{ + GeoData: geoData, + WhoIsData: whoisData, + Error: errMsg, + BaseURL: baseURL, + DBVersion: dbFilesVersion, + } - data := TemplateData{ - GeoData: geoData, - WhoIsData: whoisData, - Error: errMsg, - BaseURL: baseURL, - DBVersion: dbFilesVersion, - } - - err := templates.ExecuteTemplate(w, "index.html", data) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } + err := templates.ExecuteTemplate(w, "index.html", data) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } } func apiCurrentHandler(w http.ResponseWriter, r *http.Request) { @@ -292,15 +293,15 @@ func apiHandler(w http.ResponseWriter, r *http.Request) { func lookupHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) ipStr := vars["ip"] - baseURL := getBaseURL(r) + baseURL := getBaseURL(r) // Проверка IP ip := net.ParseIP(ipStr) if ip == nil { // Если IP невалидный, показываем ошибку на странице templates.ExecuteTemplate(w, "index.html", TemplateData{ - Error: "Invalid IP address: " + ipStr, - BaseURL: baseURL, + Error: "Invalid IP address: " + ipStr, + BaseURL: baseURL, }) return } @@ -310,8 +311,8 @@ func lookupHandler(w http.ResponseWriter, r *http.Request) { geoData, err := getGeoData(ip, preferredLocale) if err != nil { templates.ExecuteTemplate(w, "index.html", TemplateData{ - Error: err.Error(), - BaseURL: baseURL, + Error: err.Error(), + BaseURL: baseURL, }) return } @@ -320,16 +321,16 @@ func lookupHandler(w http.ResponseWriter, r *http.Request) { if err != nil { jsonData = []byte("{\"error\": \"Failed to generate JSON\"}") } - - whoisData := getWhoIsInfo(ipStr) + + whoisData := getWhoIsInfo(ipStr) // Передаем данные в шаблон templates.ExecuteTemplate(w, "index.html", TemplateData{ - GeoData: geoData, - JSONData: string(jsonData), - WhoIsData: whoisData, - BaseURL: baseURL, - DBVersion: dbFilesVersion, + GeoData: geoData, + JSONData: string(jsonData), + WhoIsData: whoisData, + BaseURL: baseURL, + DBVersion: dbFilesVersion, }) } @@ -347,32 +348,33 @@ func getPreferredLocale(r *http.Request) string { preferred := tags[0] base, _ := preferred.Base() - + locale := base.String() - + supportedLocales := []string{"en", "de", "es", "fr", "ja", "pt-BR", "ru", "zh-CN"} - + for _, supported := range supportedLocales { if locale == supported { return locale } } - + // Возвращем en если ничего другого не найдено return "en" } + func sendJSONResponse(w http.ResponseWriter, data interface{}) { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("Access-Control-Allow-Origin", "*") - + // Pretty print JSON with indentation jsonData, err := json.MarshalIndent(data, "", " ") if err != nil { sendJSONError(w, "Failed to encode JSON response", http.StatusInternalServerError) return } - + w.Write(jsonData) } @@ -380,7 +382,7 @@ func sendJSONError(w http.ResponseWriter, message string, statusCode int) { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("Access-Control-Allow-Origin", "*") w.WriteHeader(statusCode) - + errorResponse := map[string]interface{}{ "error": map[string]string{ "message": message, @@ -388,7 +390,7 @@ func sendJSONError(w http.ResponseWriter, message string, statusCode int) { }, "success": false, } - + jsonData, _ := json.MarshalIndent(errorResponse, "", " ") w.Write(jsonData) } @@ -401,7 +403,7 @@ func getClientIP(r *http.Request) string { if realIP := r.Header.Get("X-Real-IP"); realIP != "" { return realIP } - + host, _, err := net.SplitHostPort(r.RemoteAddr) if err != nil { return r.RemoteAddr @@ -414,41 +416,41 @@ func getLocalizedString(names map[string]string, preferredLocale string) string if names == nil { return "" } - + // Определяем локаль if name, exists := names[preferredLocale]; exists && name != "" { return name } - + // Переключаем на en if name, exists := names["en"]; exists { return name } - + // Если нет Английского возвращем пустоту for _, name := range names { if name != "" { return name } } - + return "" } func hasRegisteredCountryData(registeredCountry struct { - Names map[string]string `maxminddb:"names"` - IsoCode string `maxminddb:"iso_code"` - GeoNameID uint `maxminddb:"geoname_id"` - IsInEuropeanUnion bool `maxminddb:"is_in_european_union"` + Names map[string]string `maxminddb:"names"` + IsoCode string `maxminddb:"iso_code"` + GeoNameID uint `maxminddb:"geoname_id"` + IsInEuropeanUnion bool `maxminddb:"is_in_european_union"` }) bool { return registeredCountry.IsoCode != "" || len(registeredCountry.Names) > 0 } // Получаем данные из БД MaxMind func getGeoData(ip net.IP, preferredLocale string) (*GeoData, error) { - dbMutex.RLock() - defer dbMutex.RUnlock() - + dbMutex.RLock() + defer dbMutex.RUnlock() + data := &GeoData{} data.IPAddress = ip.String() data.Metadata.LocaleUsed = preferredLocale @@ -456,11 +458,11 @@ func getGeoData(ip net.IP, preferredLocale string) (*GeoData, error) { // Получаем данные из БД City if city, err := cityDB.City(ip); err == nil { data.Metadata.DataSource = "city" - + // Сперва определяем страну (main country) data.Location.Country = getLocalizedString(city.Country.Names, preferredLocale) data.Codes.CountryISO = city.Country.IsoCode - + // Если (main country) пустое то пробуем (registered country) if data.Location.Country == "" && hasRegisteredCountryData(city.RegisteredCountry) { data.Location.Country = getLocalizedString(city.RegisteredCountry.Names, preferredLocale) @@ -468,11 +470,11 @@ func getGeoData(ip net.IP, preferredLocale string) (*GeoData, error) { data.Codes.CountryISO = city.RegisteredCountry.IsoCode } } - + // Определяем континет (main continent) data.Location.Continent = getLocalizedString(city.Continent.Names, preferredLocale) data.Codes.ContinentISO = city.Continent.Code - + // Город и данные местоположения data.Location.City = getLocalizedString(city.City.Names, preferredLocale) data.Location.Latitude = city.Location.Latitude @@ -481,7 +483,7 @@ func getGeoData(ip net.IP, preferredLocale string) (*GeoData, error) { data.Location.PostalCode = city.Postal.Code data.Security.IsAnonymousProxy = city.Traits.IsAnonymousProxy data.Security.IsSatelliteProvider = city.Traits.IsSatelliteProvider - + // Регион if len(city.Subdivisions) > 0 { subdivision := city.Subdivisions[0] @@ -496,19 +498,19 @@ func getGeoData(ip net.IP, preferredLocale string) (*GeoData, error) { if data.Metadata.DataSource == "" { data.Metadata.DataSource = "country" } - - // Сперва определяем страну (main country) + + // Сперва определяем страну (main country) countryName := getLocalizedString(country.Country.Names, preferredLocale) countryISO := country.Country.IsoCode - - // Если (main country) пустое то пробуем (registered country) + + // Если (main country) пустое то пробуем (registered country) if countryName == "" && hasRegisteredCountryData(country.RegisteredCountry) { countryName = getLocalizedString(country.RegisteredCountry.Names, preferredLocale) if countryISO == "" { countryISO = country.RegisteredCountry.IsoCode } } - + // Обновлем данные если что нашли if countryName != "" { data.Location.Country = countryName @@ -516,18 +518,18 @@ func getGeoData(ip net.IP, preferredLocale string) (*GeoData, error) { if countryISO != "" { data.Codes.CountryISO = countryISO } - + // Континент continentName := getLocalizedString(country.Continent.Names, preferredLocale) continentISO := country.Continent.Code - + if continentName != "" { data.Location.Continent = continentName } if continentISO != "" { data.Codes.ContinentISO = continentISO } - + // Прокси data.Security.IsAnonymousProxy = country.Traits.IsAnonymousProxy data.Security.IsSatelliteProvider = country.Traits.IsSatelliteProvider @@ -545,31 +547,32 @@ func getGeoData(ip net.IP, preferredLocale string) (*GeoData, error) { // Определяем базовый URL сайта где запуцщен проект func getBaseURL(r *http.Request) string { - scheme := "http" - if r.TLS != nil || r.Header.Get("X-Forwarded-Proto") == "https" { - scheme = "https" - } - - host := r.Host - // Убираем порт если это стандартный порт - if strings.HasSuffix(host, ":80") && scheme == "http" { - host = strings.TrimSuffix(host, ":80") - } else if strings.HasSuffix(host, ":443") && scheme == "https" { - host = strings.TrimSuffix(host, ":443") - } - - return scheme + "://" + host + scheme := "http" + if r.TLS != nil || r.Header.Get("X-Forwarded-Proto") == "https" { + scheme = "https" + } + + host := r.Host + // Убираем порт если это стандартный порт + if strings.HasSuffix(host, ":80") && scheme == "http" { + host = strings.TrimSuffix(host, ":80") + } else if strings.HasSuffix(host, ":443") && scheme == "https" { + host = strings.TrimSuffix(host, ":443") + } + + return scheme + "://" + host } // Запрос информации с серверов whois func getWhoIsInfo(address string) string { - result, err := whois.Whois(address) - - if err != nil { + result, err := whois.Whois(address) + + if err != nil { return "WhoIs request error" } - return result + + return result } // Загрузка конфигурации @@ -590,188 +593,191 @@ func getEnv(key, defaultValue string) string { return defaultValue } + // Запуск задания скачивания файлов func sheduler() { - // инициализируем объект планировщика - s := gocron.NewScheduler(time.UTC) - // добавляем одну задачу на каждую минуту - s.Cron("30 2 * * *").Do(startDownload) - // s.Cron("*/1 * * * *").Do(startDownload) - // запускаем планировщик с блокировкой текущего потока - s.StartBlocking() + // инициализируем объект планировщика + s := gocron.NewScheduler(time.UTC) + // добавляем одну задачу на каждую минуту + s.Cron("30 2 * * *").Do(startDownload) + // s.Cron("*/1 * * * *").Do(startDownload) + // запускаем планировщик с блокировкой текущего потока + s.StartBlocking() } // Отслеживает прогресс загрузки type WriteCounter struct { - Total uint64 + Total uint64 } // Параллельный запуск загрузки БД func startDownload() { - go mmdbDownload("GeoLite2-City.mmdb") - go mmdbDownload("GeoLite2-Country.mmdb") - go mmdbDownload("GeoLite2-ASN.mmdb") + go mmdbDownload("GeoLite2-City.mmdb") + go mmdbDownload("GeoLite2-Country.mmdb") + go mmdbDownload("GeoLite2-ASN.mmdb") } func (wc *WriteCounter) Write(p []byte) (int, error) { - n := len(p) - wc.Total += uint64(n) - wc.PrintProgress() - return n, nil + n := len(p) + wc.Total += uint64(n) + wc.PrintProgress() + return n, nil } func (wc WriteCounter) PrintProgress() { - fmt.Printf("\rЗагружено %d байт...", wc.Total) + fmt.Printf("\rЗагружено %d байт...", wc.Total) } // Проверка целостности файла. func isValidMMDB(filePath string) bool { - db, err := geoip2.Open(filePath) - if err != nil { - return false - } - db.Close() - return true + db, err := geoip2.Open(filePath) + if err != nil { + return false + } + db.Close() + return true } // Проверяем контрольную сумму локального файла func getFileChecksum(filePath string) (string, error) { - file, err := os.Open(filePath) - if err != nil { - return "", err - } - defer file.Close() - - hash := sha256.New() - if _, err := io.Copy(hash, file); err != nil { - return "", err - } - - return hex.EncodeToString(hash.Sum(nil)), nil + file, err := os.Open(filePath) + if err != nil { + return "", err + } + defer file.Close() + + hash := sha256.New() + if _, err := io.Copy(hash, file); err != nil { + return "", err + } + + return hex.EncodeToString(hash.Sum(nil)), nil } // Проверяем контрольную сумму удаленного файла (скачиваемого) func getRemoteChecksum(url string) string { - resp, err := http.Get(url) - if err != nil { - return "" - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return "" - } - - body, err := io.ReadAll(resp.Body) - if err != nil { - return "" - } - - // Предполагаем, что файл содержит только хеш - return strings.TrimSpace(string(body)) + resp, err := http.Get(url) + if err != nil { + return "" + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return "" + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return "" + } + // Так как запрос возвращает сумму и путь к файлу то + // разбиваем строку на массив и берем первый элемент + words := strings.Fields(string(body)) + checkSum := words[0] + + return strings.TrimSpace(checkSum) } func shouldDownload(fileName string) bool { - log.Printf("Проверяем контрольную сумму файла %s", fileName) - fileURL := cfg.MMDBURL + fileName - filePath := filepath.Join(cfg.MMDBLocalPath, fileName) - - // Если файла нет, нужно скачивать - if _, err := os.Stat(filePath); os.IsNotExist(err) { - return true - } - - // Проверяем возраст файла (скачиваем если старше 7 дней) - /* - if info, err := os.Stat(filePath); err == nil { - if time.Since(info.ModTime()) > 7*24*time.Hour { - return true - } - } - - */ - // Проверка по контрольной сумме - if checksumURL := fileURL + ".sha256"; checksumURL != "" { - remoteFileChecksum := getRemoteChecksum(checksumURL) - // fmt.Println("Remote", remoteFileChecksum) - - if remoteFileChecksum != "" { - localFileChecksum, error := getFileChecksum(filePath) - if error == nil { - if localFileChecksum != "" { - fmt.Println(filePath, "Remote:", remoteFileChecksum, "Local:", localFileChecksum) - fmt.Println(remoteFileChecksum) - fmt.Println(localFileChecksum) - if localFileChecksum != remoteFileChecksum { - return false - } - } - } else { - fmt.Println("Error", filePath, error) - return true - } - } - } - - return false + log.Printf("Проверяем контрольную сумму файла %s", fileName) + fileURL := cfg.MMDBURL + fileName + filePath := filepath.Join(cfg.MMDBLocalPath, fileName) + + // Если файла нет, нужно скачивать + if _, err := os.Stat(filePath); os.IsNotExist(err) { + return true + } + + // Проверяем возраст файла (скачиваем если старше 7 дней) +/* + if info, err := os.Stat(filePath); err == nil { + if time.Since(info.ModTime()) > 7*24*time.Hour { + return true + } + } + +*/ + // Проверка по контрольной сумме + if checksumURL := fileURL + ".sha256"; checksumURL != "" { + remoteFileChecksum := getRemoteChecksum(checksumURL) + // fmt.Println("Remote", remoteFileChecksum) + + if remoteFileChecksum != "" { + localFileChecksum, error := getFileChecksum(filePath) + if error == nil { + if localFileChecksum != "" { + fmt.Println(filePath, "Remote:", remoteFileChecksum, "Local:", localFileChecksum) + fmt.Println(remoteFileChecksum) + fmt.Println(localFileChecksum) + if localFileChecksum != remoteFileChecksum { + return true + } + } + } else { + fmt.Println("Error", filePath, error) + return true + } + } + } + + return false } - // Загрузка файлов БД с ВЭБ-сервера func mmdbDownload(fileName string) { - fileURL := cfg.MMDBURL + fileName - filePath := filepath.Join(cfg.MMDBLocalPath, fileName) + fileURL := cfg.MMDBURL + fileName + filePath := filepath.Join(cfg.MMDBLocalPath, fileName) + + if !shouldDownload(fileName) { + log.Printf("Файл %s актуален, пропускаем загрузку", fileName) + return + } + + // Скачиваем во временный файл + tempPath := filePath + ".tmp" + + log.Println("Загружается файл:",fileURL) + // Создаём файл + file, err := os.Create(tempPath) + if err != nil { + log.Printf("Ошибка создания файла %s: %v", tempPath, err) + return + } + defer file.Close() - if !shouldDownload(fileName) { - log.Printf("Файл %s актуален, пропускаем загрузку", fileName) - return - } + // Загружаем данные с отслеживанием прогресса + counter := &WriteCounter{} + response, err := http.Get(fileURL) + if err != nil { + log.Printf("Ошибка загрузки файла %s: %v", fileURL, err) + return + } + defer response.Body.Close() - // Скачиваем во временный файл - tempPath := filePath + ".tmp" + if response.StatusCode != http.StatusOK { + log.Printf("Ошибка загрузки: %s", response.Status) + return + } - log.Println("Загружается файл:", fileURL) - // Создаём файл - file, err := os.Create(tempPath) - if err != nil { - log.Printf("Ошибка создания файла %s: %v", tempPath, err) - return - } - defer file.Close() + // Копируем через буфер с отслеживанием прогресса + _, err = io.Copy(file, io.TeeReader(response.Body, counter)) + if err != nil { + log.Printf("Ошибка копирования данных: %v", err) + return + } - // Загружаем данные с отслеживанием прогресса - counter := &WriteCounter{} - response, err := http.Get(fileURL) - if err != nil { - log.Printf("Ошибка загрузки файла %s: %v", fileURL, err) - return - } - defer response.Body.Close() - - if response.StatusCode != http.StatusOK { - log.Printf("Ошибка загрузки: %s", response.Status) - return - } - - // Копируем через буфер с отслеживанием прогресса - _, err = io.Copy(file, io.TeeReader(response.Body, counter)) - if err != nil { - log.Printf("Ошибка копирования данных: %v", err) - return - } - - log.Println("Загрузка завершена! Файл сохранен:", tempPath) - - // Переоткрываем базы данных после успешной загрузки - // reopenDBs(fileName) - // Проверяем валидность перед заменой - if isValidMMDB(tempPath) { - // Заменяем старый файл - os.Rename(tempPath, filePath) - // Переоткрываем только эту базу - reopenDBs(fileName) - } else { - log.Printf("Скачанный файл %s поврежден, удаляем", fileName) - os.Remove(tempPath) - } + log.Println("Загрузка завершена! Файл сохранен:", tempPath) + + // Переоткрываем базы данных после успешной загрузки + // reopenDBs(fileName) + // Проверяем валидность перед заменой + if isValidMMDB(tempPath) { + // Заменяем старый файл + os.Rename(tempPath, filePath) + // Переоткрываем только эту базу + reopenDBs(fileName) + } else { + log.Printf("Скачанный файл %s поврежден, удаляем", fileName) + os.Remove(tempPath) + } }