hostSync/core/hosts.go

397 lines
9.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package core
import (
"bufio"
"fmt"
"os"
"regexp"
"sort"
"strings"
"time"
"github.com/evil7/hostsync/config"
"github.com/evil7/hostsync/utils"
)
// HostEntry hosts条目
type HostEntry struct {
IP string
Domain string
Comment string
Enabled bool
}
// HostBlock hosts块
type HostBlock struct {
Name string
Entries []HostEntry
DNS string
DoH string
Server string
CronJob string
UpdateAt time.Time
Enabled bool
Comments []string
}
// HostsManager hosts文件管理器
type HostsManager struct {
FilePath string
Blocks map[string]*HostBlock
Others []string // 非块内容
}
var (
blockStartPattern = regexp.MustCompile(`^#\s*([a-zA-Z0-9_-]+):\s*$`)
blockEndPattern = regexp.MustCompile(`^#\s*([a-zA-Z0-9_-]+);\s*$`)
commentPattern = regexp.MustCompile(`^#\s*(\w+):\s*(.*)$`)
hostPattern = regexp.MustCompile(`^#?\s*([^\s#]+)\s+([^\s#]+)(?:\s*#(.*))?$`)
)
// NewHostsManager 创建hosts管理器
func NewHostsManager() *HostsManager {
return &HostsManager{
FilePath: config.AppConfig.HostsPath,
Blocks: make(map[string]*HostBlock),
Others: make([]string, 0),
}
}
// Load 加载hosts文件
func (hm *HostsManager) Load() error {
if !utils.FileExists(hm.FilePath) {
return fmt.Errorf("hosts文件不存在: %s", hm.FilePath)
}
file, err := os.Open(hm.FilePath)
if err != nil {
return fmt.Errorf("打开hosts文件失败: %v", err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
var currentBlock *HostBlock
var inBlock bool
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
// 先检查是否是配置注释(在块内)
if inBlock && currentBlock != nil {
if matches := commentPattern.FindStringSubmatch(line); matches != nil {
// 这是配置注释,处理块内容
hm.parseBlockLine(line, currentBlock)
continue
}
}
// 检查块开始
if matches := blockStartPattern.FindStringSubmatch(line); matches != nil {
blockName := matches[1]
currentBlock = &HostBlock{
Name: blockName,
Entries: make([]HostEntry, 0),
Comments: make([]string, 0),
Enabled: true,
}
hm.Blocks[blockName] = currentBlock
inBlock = true
continue
}
// 检查块结束
if matches := blockEndPattern.FindStringSubmatch(line); matches != nil {
inBlock = false
currentBlock = nil
continue
}
if inBlock && currentBlock != nil {
// 处理块内容
hm.parseBlockLine(line, currentBlock)
} else {
// 非块内容 - 只保存非空行
originalLine := scanner.Text()
if strings.TrimSpace(originalLine) != "" {
hm.Others = append(hm.Others, originalLine)
}
}
}
return scanner.Err()
}
// parseBlockLine 解析块内的行
func (hm *HostsManager) parseBlockLine(line string, block *HostBlock) {
originalLine := line
trimmedLine := strings.TrimSpace(line)
// 检查是否是配置注释
if matches := commentPattern.FindStringSubmatch(trimmedLine); matches != nil {
key, value := matches[1], matches[2]
switch key {
case "useDns":
block.DNS = value
case "useDoh":
block.DoH = value
case "useSrv":
block.Server = value
case "cronJob":
block.CronJob = value
case "updateAt":
if t, err := time.Parse("2006-01-02 15:04:05", value); err == nil {
block.UpdateAt = t
}
}
return
}
// 检查是否是host条目包括被注释的
if matches := hostPattern.FindStringSubmatch(trimmedLine); matches != nil {
ip, domain := matches[1], matches[2]
comment := ""
enabled := true
if len(matches) > 3 && matches[3] != "" {
comment = strings.TrimSpace(matches[3])
}
// 检查是否被注释掉(以 # 开头)
if strings.HasPrefix(trimmedLine, "#") {
enabled = false
// 需要从注释中提取实际的 IP 和域名
// 重新解析去掉开头 # 后的内容
lineWithoutComment := strings.TrimSpace(strings.TrimPrefix(trimmedLine, "#"))
if newMatches := regexp.MustCompile(`^([^\s#]+)\s+([^\s#]+)(?:\s*#(.*))?$`).FindStringSubmatch(lineWithoutComment); newMatches != nil {
ip, domain = newMatches[1], newMatches[2]
if len(newMatches) > 3 && newMatches[3] != "" {
comment = strings.TrimSpace(newMatches[3])
}
}
}
entry := HostEntry{
IP: ip,
Domain: domain,
Comment: comment,
Enabled: enabled,
}
block.Entries = append(block.Entries, entry)
return
} // 其他注释
if strings.HasPrefix(trimmedLine, "#") {
block.Comments = append(block.Comments, originalLine)
}
}
// Save 保存hosts文件
func (hm *HostsManager) Save() error {
// 备份原文件
if err := utils.BackupFile(hm.FilePath); err != nil {
return fmt.Errorf("备份文件失败: %v", err)
}
file, err := os.Create(hm.FilePath)
if err != nil {
return fmt.Errorf("创建hosts文件失败: %v", err)
}
defer file.Close()
// 写入非块内容 (过滤空行并确保末尾只有一个空行)
hasNonBlockContent := false
for _, line := range hm.Others {
line = strings.TrimSpace(line)
if line != "" {
if hasNonBlockContent {
fmt.Fprintln(file) // 只在有内容时添加空行分隔
}
fmt.Fprintln(file, line)
hasNonBlockContent = true
}
}
// 写入块内容
blockNames := make([]string, 0, len(hm.Blocks))
for name := range hm.Blocks {
blockNames = append(blockNames, name)
}
sort.Strings(blockNames)
for i, name := range blockNames {
block := hm.Blocks[name]
// 在块之间添加空行分隔(除了第一个块且前面有非块内容)
if i > 0 || hasNonBlockContent {
fmt.Fprintln(file)
}
hm.writeBlock(file, block)
}
return nil
}
// writeBlock 写入块内容
func (hm *HostsManager) writeBlock(file *os.File, block *HostBlock) {
fmt.Fprintf(file, "# %s:\n", block.Name)
// 写入hosts条目
for _, entry := range block.Entries {
prefix := ""
if !entry.Enabled {
prefix = "# "
}
if entry.Comment != "" {
fmt.Fprintf(file, "%s%-16s %s # %s\n", prefix, entry.IP, entry.Domain, entry.Comment)
} else {
fmt.Fprintf(file, "%s%-16s %s\n", prefix, entry.IP, entry.Domain)
}
}
// 写入配置注释
if block.DNS != "" {
fmt.Fprintf(file, "# useDns: %s\n", block.DNS)
}
if block.DoH != "" {
fmt.Fprintf(file, "# useDoh: %s\n", block.DoH)
}
if block.Server != "" {
fmt.Fprintf(file, "# useSrv: %s\n", block.Server)
}
if block.CronJob != "" {
fmt.Fprintf(file, "# cronJob: %s\n", block.CronJob)
}
if !block.UpdateAt.IsZero() {
fmt.Fprintf(file, "# updateAt: %s\n", block.UpdateAt.Format("2006-01-02 15:04:05"))
}
// 写入其他注释
for _, comment := range block.Comments {
fmt.Fprintln(file, comment)
}
fmt.Fprintf(file, "# %s;\n", block.Name)
}
// GetBlock 获取指定块
func (hm *HostsManager) GetBlock(name string) *HostBlock {
return hm.Blocks[name]
}
// CreateBlock 创建新块
func (hm *HostsManager) CreateBlock(name string) *HostBlock {
if !utils.ValidateBlockName(name) {
return nil
}
if _, exists := hm.Blocks[name]; exists {
return hm.Blocks[name]
}
block := &HostBlock{
Name: name,
Entries: make([]HostEntry, 0),
Comments: make([]string, 0),
Enabled: true,
}
hm.Blocks[name] = block
return block
}
// DeleteBlock 删除块
func (hm *HostsManager) DeleteBlock(name string) error {
if _, exists := hm.Blocks[name]; !exists {
return fmt.Errorf("块不存在: %s", name)
}
delete(hm.Blocks, name)
return nil
}
// EnableBlock 启用块
func (hm *HostsManager) EnableBlock(name string) error {
block := hm.GetBlock(name)
if block == nil {
return fmt.Errorf("块不存在: %s", name)
}
for i := range block.Entries {
block.Entries[i].Enabled = true
}
block.Enabled = true
return hm.Save()
}
// DisableBlock 禁用块
func (hm *HostsManager) DisableBlock(name string) error {
block := hm.GetBlock(name)
if block == nil {
return fmt.Errorf("块不存在: %s", name)
}
for i := range block.Entries {
block.Entries[i].Enabled = false
}
block.Enabled = false
return hm.Save()
}
// AddEntry 添加条目
func (hm *HostsManager) AddEntry(blockName, domain, ip string) error {
block := hm.GetBlock(blockName)
if block == nil {
block = hm.CreateBlock(blockName)
if block == nil {
return fmt.Errorf("无法创建块: %s", blockName)
}
}
// 检查是否已存在
for i, entry := range block.Entries {
if entry.Domain == domain {
// 更新IP
block.Entries[i].IP = ip
block.Entries[i].Enabled = true
return hm.Save()
}
}
// 添加新条目
entry := HostEntry{
IP: ip,
Domain: domain,
Enabled: true,
}
block.Entries = append(block.Entries, entry)
return hm.Save()
}
// RemoveEntry 删除条目
func (hm *HostsManager) RemoveEntry(blockName, domain string) error {
block := hm.GetBlock(blockName)
if block == nil {
return fmt.Errorf("块不存在: %s", blockName)
}
for i, entry := range block.Entries {
if entry.Domain == domain {
// 删除条目
block.Entries = append(block.Entries[:i], block.Entries[i+1:]...)
return hm.Save()
}
}
return fmt.Errorf("域名不存在: %s", domain)
}
// UpdateBlockTime 更新块的更新时间
func (hm *HostsManager) UpdateBlockTime(blockName string) error {
block := hm.GetBlock(blockName)
if block == nil {
return fmt.Errorf("块不存在: %s", blockName)
}
block.UpdateAt = time.Now()
return hm.Save()
}