diff --git a/config.go b/config.go index 702b40a..b876958 100644 --- a/config.go +++ b/config.go @@ -57,12 +57,12 @@ type ConfigStruct struct { SyncServerURL string SyncServerToken string BlockList []string - BlockListURL string - BlockListFile string + BlockListURL []string + BlockListFile []string PortBlockList []uint32 IPBlockList []string - IPBlockListURL string - IPBlockListFile string + IPBlockListURL []string + IPBlockListFile []string IgnoreByDownloaded uint32 GenIPDat uint32 IPUploadedCheck bool @@ -92,12 +92,12 @@ var needHideWindow bool var needHideSystray bool var randomStrRegexp = regexp2.MustCompile("[a-zA-Z0-9]{32}", 0) -var blockListCompiled []*regexp2.Regexp -var blockListFromURLCompiled = make(map[string]*regexp2.Regexp) -var blockListFromFileCompiled = make(map[string]*regexp2.Regexp) -var ipBlockListCompiled []*net.IPNet -var ipBlockListFromURLCompiled = make(map[string]*net.IPNet) -var ipBlockListFromFileCompiled = make(map[string]*net.IPNet) +var blockListCompiled = make(map[string]*regexp2.Regexp) +var ipBlockListCompiled = make(map[string]*net.IPNet) +var blockListURLLastFetch int64 = 0 +var ipBlockListURLLastFetch int64 = 0 +var blockListFileLastMod = make(map[string]int64) +var ipBlockListFileLastMod = make(map[string]int64) var cookieJar, _ = cookiejar.New(nil) var lastURL = "" @@ -108,10 +108,6 @@ var longFlag_configFilename string var additionConfigFilename string = "config_additional.json" var shortFlag_additionConfigFilename string var longFlag_additionConfigFilename string -var blockListURLLastFetch int64 = 0 -var ipBlockListURLLastFetch int64 = 0 -var blockListFileLastMod int64 = 0 -var ipBlockListFileLastMod int64 = 0 var httpTransport = &http.Transport{ DisableKeepAlives: true, @@ -174,12 +170,12 @@ var config = ConfigStruct{ SyncServerURL: "", SyncServerToken: "", BlockList: []string{}, - BlockListURL: "", - BlockListFile: "", + BlockListURL: []string{}, + BlockListFile: []string{}, PortBlockList: []uint32{}, IPBlockList: []string{}, - IPBlockListURL: "", - IPBlockListFile: "", + IPBlockListURL: nil, + IPBlockListFile: nil, IgnoreByDownloaded: 100, GenIPDat: 0, IPUploadedCheck: false, @@ -197,184 +193,239 @@ var config = ConfigStruct{ BanByRelativePUAntiErrorRatio: 3, } -func SetBlockListFromContent(blockListContent []byte, blockListCompiled map[string]*regexp2.Regexp) int { - // Max 8MB. - if len(blockListContent) > 8388608 { - Log("SetBlockListFromContent", GetLangText("Error-LargeFile"), true) - return 0 - } - - blockListArr := strings.Split(string(blockListContent), "\n") - tmpBlockListCompiled := make(map[string]*regexp2.Regexp) +func SetBlockListFromContent(blockListContent []string) int { + setCount := 0 - for blockListLineNum, blockListLine := range blockListArr { - blockListLine = ProcessRemark(blockListLine) - if blockListLine == "" { - Log("Debug-SetBlockListFromContent_Compile", GetLangText("Error-Debug-EmptyLine"), false, blockListLineNum) + for index, content := range blockListContent { + content = StrTrim(ProcessRemark(content)) + if content == "" { + Log("Debug-SetBlockListFromContent_Compile", GetLangText("Error-Debug-EmptyLine"), false, index) continue } - if reg, exists := blockListCompiled[blockListLine]; exists { - tmpBlockListCompiled[blockListLine] = reg + if _, exists := blockListCompiled[content]; exists { continue } - Log("Debug-SetBlockListFromContent_Compile", ":%d %s", false, blockListLineNum, blockListLine) + Log("Debug-SetBlockListFromContent_Compile", ":%d %s", false, index, content) - reg, err := regexp2.Compile("(?i)"+blockListLine, 0) + reg, err := regexp2.Compile("(?i)"+content, 0) if err != nil { - Log("SetBlockListFromContent_Compile", GetLangText("Error-SetBlockListFromContent_Compile"), true, blockListLineNum, blockListLine) + Log("SetBlockListFromContent_Compile", GetLangText("Error-SetBlockListFromContent_Compile"), true, index, content) continue } reg.MatchTimeout = 50 * time.Millisecond - tmpBlockListCompiled[blockListLine] = reg + blockListCompiled[content] = reg + setCount++ } - blockListCompiled = tmpBlockListCompiled - return len(blockListCompiled) + return setCount } -func SetIPBlockListFromContent(ipBlockListContent []byte, ipBlockListCompiled map[string]*net.IPNet) int { - // Max 8MB. - if len(ipBlockListContent) > 8388608 { - Log("SetIPBlockListFromContent", GetLangText("Error-LargeFile"), true) - return 0 +func SetBlockListFromFile() bool { + if config.BlockListFile == nil || len(config.BlockListFile) == 0 { + return true } - - ipBlockListArr := strings.Split(string(ipBlockListContent), "\n") - tmpIPBlockListCompiled := make(map[string]*net.IPNet) - - for ipBlockListLineNum, ipBlockListLine := range ipBlockListArr { - ipBlockListLine = ProcessRemark(ipBlockListLine) - if ipBlockListLine == "" { - Log("Debug-SetIPBlockListFromContent_Compile", GetLangText("Error-Debug-EmptyLine"), false, ipBlockListLineNum) + setCount := 0 + for _, FilePath := range config.BlockListFile { + blockListFileStat, err := os.Stat(FilePath) + // Max 8MB. + if blockListFileStat.Size() > 8388608 { + Log("SetBlockListFromFile", GetLangText("Error-LargeFile"), true) continue } - - if cidr, exists := ipBlockListCompiled[ipBlockListLine]; exists { - tmpIPBlockListCompiled[ipBlockListLine] = cidr - continue + if err != nil { + Log("SetBlockListFromFile", GetLangText("Error-LoadFile"), false, FilePath, err.Error()) + return false } - Log("Debug-SetIPBlockListFromContent_Compile", ":%d %s", false, ipBlockListLineNum, ipBlockListLine) - cidr := ParseIPCIDR(ipBlockListLine) - if cidr == nil { - Log("SetIPBlockListFromContent_Compile", GetLangText("Error-SetIPBlockListFromContent_Compile"), true, ipBlockListLineNum, ipBlockListLine) - continue + FileLastMod := blockListFileStat.ModTime().Unix() + if FileLastMod == blockListFileLastMod[FilePath] { + return false } - - tmpIPBlockListCompiled[ipBlockListLine] = cidr + if blockListFileLastMod[FilePath] != 0 { + Log("Debug-SetBlockListFromFile", GetLangText("Debug-SetBlockListFromFile_HotReload"), false, FilePath) + } + blockListContent, err := os.ReadFile(FilePath) + if err != nil { + Log("SetBlockListFromFile", GetLangText("Error-LoadFile"), true, FilePath, err.Error()) + return false + } + blockListFileLastMod[FilePath] = FileLastMod + + var Content []string + if filepath.Ext(FilePath) == ".json" { + err = json.Unmarshal(blockListContent, &Content) + if err != nil { + Log("SetBlockListFromFile", GetLangText("Error-GenJSON"), true, FilePath) + continue + } + } else { + Content = strings.Split(string(blockListContent), "\n") + } + setCount += SetBlockListFromContent(Content) } - ipBlockListCompiled = tmpIPBlockListCompiled - return len(ipBlockListCompiled) + Log("SetBlockListFromFile", GetLangText("Success-SetBlockListFromFile"), true, setCount) + return true } func SetBlockListFromURL() bool { - if config.BlockListURL == "" || (blockListURLLastFetch+int64(config.UpdateInterval)) > currentTimestamp { + if config.BlockListURL == nil || len(config.BlockListURL) == 0 || (blockListURLLastFetch+int64(config.UpdateInterval)) > currentTimestamp { return true } - blockListURLLastFetch = currentTimestamp - _, _, blockListContent := Fetch(config.BlockListURL, false, false, nil) - if blockListContent == nil { - blockListURLLastFetch -= (int64(config.UpdateInterval) + 900) - Log("SetBlockListFromURL", GetLangText("Error-FetchResponse2"), true) - return false - } - - ruleCount := SetBlockListFromContent(blockListContent, blockListFromURLCompiled) + setCountChan := make(chan int, len(config.BlockListURL)) - Log("SetBlockListFromURL", GetLangText("Success-SetBlockListFromURL"), true, ruleCount) + for _, BlockListURL := range config.BlockListURL { + WaitGroup.Add(1) + go func(BlockListURL string) { + defer WaitGroup.Done() + _, httpHeader, blockListContent := Fetch(BlockListURL, false, false, nil) - return true -} -func SetIPBlockListFromURL() bool { - if config.IPBlockListURL == "" || (ipBlockListURLLastFetch+int64(config.UpdateInterval)) > currentTimestamp { - return true + if blockListContent == nil { + blockListURLLastFetch -= (int64(config.UpdateInterval) + 900) + Log("SetBlockListFromURL", GetLangText("Error-FetchResponse2"), true) + return + } + if len(blockListContent) > 8388608 { + Log("SetBlockListFromURL", GetLangText("Error-LargeFile"), true) + return + } + var Content []string + if strings.HasSuffix(httpHeader.Get("Content-Type"), "json") { + err := json.Unmarshal(blockListContent, &Content) + if err != nil { + Log("SetBlockListFromFile", GetLangText("Error-GenJSON"), true, BlockListURL) + return + } + } else { + Content = strings.Split(string(blockListContent), "\n") + } + setCountChan <- SetBlockListFromContent(Content) + }(BlockListURL) } - ipBlockListURLLastFetch = currentTimestamp + WaitGroup.Wait() + close(setCountChan) - _, _, ipBlockListContent := Fetch(config.IPBlockListURL, false, false, nil) - if ipBlockListContent == nil { - ipBlockListURLLastFetch -= (int64(config.UpdateInterval) + 900) - Log("SetIPBlockListFromURL", GetLangText("Error-FetchResponse2"), true) - return false + setCount := 0 + for count := range setCountChan { + setCount += count } - ruleCount := SetIPBlockListFromContent(ipBlockListContent, ipBlockListFromURLCompiled) - - Log("SetIPBlockListFromURL", GetLangText("Success-SetIPBlockListFromURL"), true, ruleCount) - + Log("SetBlockListFromURL", GetLangText("Success-SetBlockListFromURL"), true, setCount) return true } -func SetBlockListFromFile() bool { - if config.BlockListFile == "" { - return true - } +func SetIPBlockListFromContent(ipBlockListContent []string) int { + setCount := 0 - blockListFileStat, err := os.Stat(config.BlockListFile) - if err != nil { - Log("SetBlockListFromFile", GetLangText("Error-LoadFile"), false, config.BlockListFile, err.Error()) - return false - } + for index, content := range ipBlockListContent { + content = StrTrim(ProcessRemark(content)) + if content == "" { + Log("Debug-SetIPBlockListFromContent_Compile", GetLangText("Error-Debug-EmptyLine"), false, index) + continue + } - tmpBlockListFileLastMod := blockListFileStat.ModTime().Unix() - if tmpBlockListFileLastMod <= blockListFileLastMod { - return false - } + if _, exists := ipBlockListCompiled[content]; exists { + continue + } - if blockListFileLastMod != 0 { - Log("Debug-SetBlockListFromFile", GetLangText("Debug-SetBlockListFromFile_HotReload"), false, config.BlockListFile) - } + Log("Debug-SetIPBlockListFromContent_Compile", ":%d %s", false, index, content) + cidr := ParseIPCIDR(content) + if cidr == nil { + Log("SetIPBlockListFromContent_Compile", GetLangText("Error-SetIPBlockListFromContent_Compile"), true, index, content) + continue + } - blockListFile, err := os.ReadFile(config.BlockListFile) - if err != nil { - Log("SetBlockListFromFile", GetLangText("Error-LoadFile"), true, config.BlockListFile, err.Error()) - return false + ipBlockListCompiled[content] = cidr + setCount++ } - blockListFileLastMod = tmpBlockListFileLastMod - - ruleCount := SetBlockListFromContent(blockListFile, blockListFromFileCompiled) - - Log("SetBlockListFromFile", GetLangText("Success-SetBlockListFromFile"), true, ruleCount) - - return true + return setCount } func SetIPBlockListFromFile() bool { - if config.IPBlockListFile == "" { + if config.IPBlockListFile == nil || len(config.IPBlockListFile) == 0 { return true } + setCount := 0 + for _, FilePath := range config.IPBlockListFile { + ipBlockListFileStat, err := os.Stat(FilePath) + if err != nil { + Log("SetIPBlockListFromFile", GetLangText("Error-LoadFile"), false, FilePath, err.Error()) + return false + } - ipBlockListFileStat, err := os.Stat(config.IPBlockListFile) - if err != nil { - Log("SetIPBlockListFromFile", GetLangText("Error-LoadFile"), false, config.IPBlockListFile, err.Error()) - return false - } + FileLastMod := ipBlockListFileStat.ModTime().Unix() + if FileLastMod == ipBlockListFileLastMod[FilePath] { + return false + } + if ipBlockListFileLastMod[FilePath] != 0 { + Log("Debug-SetIPBlockListFromFile", GetLangText("Debug-SetIPBlockListFromFile_HotReload"), false, FilePath) + } + ipBlockListFile, err := os.ReadFile(FilePath) + if err != nil { + Log("SetIPBlockListFromFile", GetLangText("Error-LoadFile"), true, FilePath, err.Error()) + return false + } + ipBlockListFileLastMod[FilePath] = FileLastMod - tmpIPBlockListFileLastMod := ipBlockListFileStat.ModTime().Unix() - if tmpIPBlockListFileLastMod <= ipBlockListFileLastMod { - return false + var Content []string + if filepath.Ext(FilePath) == ".json" { + err := json.Unmarshal(ipBlockListFile, &Content) + if err != nil { + Log("SetIPBlockListFromFile", GetLangText("Error-GenJSON"), true, FilePath) + } + } else { + Content = strings.Split(string(ipBlockListFile), "\n") + } + setCount += SetIPBlockListFromContent(Content) } - - if ipBlockListFileLastMod != 0 { - Log("Debug-SetIPBlockListFromFile", GetLangText("Debug-SetIPBlockListFromFile_HotReload"), false, config.IPBlockListFile) + Log("SetIPBlockListFromFile", GetLangText("Success-SetIPBlockListFromFile"), true, setCount) + return true +} +func SetIPBlockListFromURL() bool { + if config.IPBlockListURL == nil || len(config.IPBlockListURL) == 0 || (ipBlockListURLLastFetch+int64(config.UpdateInterval)) > currentTimestamp { + return true } + ipBlockListURLLastFetch = currentTimestamp - ipBlockListFile, err := os.ReadFile(config.IPBlockListFile) - if err != nil { - Log("SetIPBlockListFromFile", GetLangText("Error-LoadFile"), true, config.IPBlockListFile, err.Error()) - return false + setCountChan := make(chan int, len(config.IPBlockListURL)) + + for _, ipBlockListURL := range config.IPBlockListURL { + WaitGroup.Add(1) + go func(ipBlockListURL string) { + defer WaitGroup.Done() + _, httpHeader, ipBlockListContent := Fetch(ipBlockListURL, false, false, nil) + if ipBlockListContent == nil { + ipBlockListURLLastFetch -= (int64(config.UpdateInterval) + 900) + Log("SetIPBlockListFromURL", GetLangText("Error-FetchResponse2"), true) + return + } + var Content []string + if strings.HasSuffix(httpHeader.Get("Content-Type"), "json") { + err := json.Unmarshal(ipBlockListContent, &Content) + if err != nil { + Log("SetIPBlockListFromURL", GetLangText("Error-GenJSON"), true, ipBlockListURL) + return + } + } else { + Content = strings.Split(string(ipBlockListContent), "\n") + } + setCountChan <- SetIPBlockListFromContent(Content) + }(ipBlockListURL) } - ipBlockListFileLastMod = tmpIPBlockListFileLastMod + WaitGroup.Wait() + close(setCountChan) - ruleCount := SetIPBlockListFromContent(ipBlockListFile, ipBlockListFromFileCompiled) + setCount := 0 + for count := range setCountChan { + setCount += count + } - Log("SetIPBlockListFromFile", GetLangText("Success-SetIPBlockListFromFile"), true, ruleCount) + Log("SetIPBlockListFromURL", GetLangText("Success-SetIPBlockListFromURL"), true, setCount) return true } @@ -477,32 +528,16 @@ func InitConfig() { Log("LoadConfig_Current", "%v: %v", false, t.Field(k).Name, v.Field(k).Interface()) } - blockListCompiled = make([]*regexp2.Regexp, len(config.BlockList)) - for k, v := range config.BlockList { - Log("Debug-LoadConfig_CompileBlockList", "%s", false, v) - - reg, err := regexp2.Compile("(?i)"+v, 0) - if err != nil { - Log("LoadConfig_CompileBlockList", GetLangText("Error-CompileBlockList"), false, v) - continue - } - - reg.MatchTimeout = 50 * time.Millisecond - - blockListCompiled[k] = reg + blockListCompiled = make(map[string]*regexp2.Regexp) + SetBlockListFromContent(config.BlockList) + if errCount := len(config.BlockList) != len(blockListCompiled); errCount { + Log("LoadConfig_CompileBlockList", GetLangText("Error-CompileBlockList"), false, errCount) } - ipBlockListCompiled = make([]*net.IPNet, len(config.IPBlockList)) - for k, v := range config.IPBlockList { - Log("Debug-LoadConfig_CompileIPBlockList", "%s", false, v) - - cidr := ParseIPCIDR(v) - if cidr == nil { - Log("LoadConfig_CompileIPBlockList", GetLangText("Error-CompileIPBlockList"), false, v) - continue - } - - ipBlockListCompiled[k] = cidr + ipBlockListCompiled = make(map[string]*net.IPNet, len(config.IPBlockList)) + SetIPBlockListFromContent(config.IPBlockList) + if errCount := len(config.IPBlockList) != len(ipBlockListCompiled); errCount { + Log("LoadConfig_CompileIPBlockList", GetLangText("Error-CompileIPBlockList"), false, errCount) } } func LoadInitConfig(firstLoad bool) bool { diff --git a/config.json b/config.json index 9497c69..372b2b0 100644 --- a/config.json +++ b/config.json @@ -21,7 +21,8 @@ "Taipei-Torrent dev", "qBittorrent[ /]3\\.3\\.15", "gobind", "offline-download", - "ljyun.cn" + "ljyun.cn", + "Gopeed" ], "_blockList": [ // 可选 blockList. @@ -56,5 +57,8 @@ "240e:90e:2000:2006::/60", "240e:918:8008::/48" ], - "ipBlockListURL": "https://raw.githubusercontent.com/PBH-BTN/BTN-Collected-Rules/main/combine/all.txt" + "ipBlockListURL": [ + "https://bta.iaalai.cn/BTN-Collected-Rules/combine/all.txt", + "https://cdn.jsdelivr.net/gh/PBH-BTN/BTN-Collected-Rules@main/combine/all.txt" + ] } diff --git a/console.go b/console.go index 94918a5..4de4ed7 100644 --- a/console.go +++ b/console.go @@ -276,7 +276,7 @@ func Task() { SubmitBlockPeer(blockPeerMap) - if !config.IPUploadedCheck && len(ipBlockListCompiled) <= 0 && len(ipBlockListFromURLCompiled) <= 0 && len(ipBlockListFromFileCompiled) <= 0 && len(ipBlockCIDRMapFromSyncServerCompiled) <= 0 { + if !config.IPUploadedCheck && len(ipBlockListCompiled) <= 0 && len(ipBlockCIDRMapFromSyncServerCompiled) <= 0 { Log("Task", GetLangText("Task_BanInfo"), true, blockCount, len(blockPeerMap)) } else { Log("Task", GetLangText("Task_BanInfoWithIP"), true, blockCount, ipBlockCount, len(blockPeerMap)) diff --git a/i18n.go b/i18n.go index 07fe9fb..b55f406 100644 --- a/i18n.go +++ b/i18n.go @@ -2,8 +2,9 @@ package main import ( "encoding/json" - "github.com/Xuanwo/go-locale" "os" + + "github.com/Xuanwo/go-locale" ) var langContent map[string]string @@ -53,7 +54,7 @@ var defaultLangContent = map[string]string{ "Error-LoadConfig": "加载配置文件 (%s) 时发生了错误: %s", "Error-ParseConfig": "解析配置文件 (%s) 时发生了错误: %s", "Error-LoadFile": "加载文件 (%s) 时发生了错误: %s", - "Error-CompileBlockList": "表达式 %s 有错误", + "Error-CompileBlockList": "表达式存在 %d 处错误", "Error-CompileIPBlockList": "IP %s 有错误", "Error-GetClientConfig_LoadConfig": "加载客户端配置文件时发生了错误: %s", "Error-GetClientConfig_LoadConfigMeta": "读取客户端配置文件元数据时发生了错误: %s", diff --git a/lang/en.json b/lang/en.json index 7aeedb0..176d9aa 100644 --- a/lang/en.json +++ b/lang/en.json @@ -43,7 +43,7 @@ "Error-LoadConfig": "An error occurred while loading config (%s): %s", "Error-ParseConfig": "An error occurred while parsing config (%s): %s", "Error-LoadFile": "An error occurred while parsing file (%s): %s", - "Error-CompileBlockList": "Expression %s has error", + "Error-CompileBlockList": "Expression has %d error", "Error-CompileIPBlockList": "IP %s has error", "Error-GetClientConfig_LoadConfig": "An error occurred while loading client config file: %s", "Error-GetClientConfig_LoadConfigMeta": "An error occurred while reading client config file: %s", diff --git a/peer.go b/peer.go index 3a8910c..746db21 100644 --- a/peer.go +++ b/peer.go @@ -1,10 +1,11 @@ package main import ( - "github.com/dlclark/regexp2" "net" "strconv" "strings" + + "github.com/dlclark/regexp2" ) type BlockPeerInfoStruct struct { @@ -196,20 +197,6 @@ func CheckPeer(peerIP string, peerPort int, peerID string, peerClient string, pe return 1, peerNet } } - for _, v := range blockListFromURLCompiled { - if MatchBlockList(v, peerIP, peerPort, peerID, peerClient) { - Log("CheckPeer_AddBlockPeer (Bad-Client_Normal|BlockListFromURL)", "%s:%d %s|%s (TorrentInfoHash: %s)", true, peerIP, peerPort, strconv.QuoteToASCII(peerID), strconv.QuoteToASCII(peerClient), torrentInfoHash) - AddBlockPeer("Bad-Client_Normal|BlockListFromURL", peerIP, peerPort, torrentInfoHash) - return 1, peerNet - } - } - for _, v := range blockListFromFileCompiled { - if MatchBlockList(v, peerIP, peerPort, peerID, peerClient) { - Log("CheckPeer_AddBlockPeer (Bad-Client_Normal|BlockListFromFile)", "%s:%d %s|%s (TorrentInfoHash: %s)", true, peerIP, peerPort, strconv.QuoteToASCII(peerID), strconv.QuoteToASCII(peerClient), torrentInfoHash) - AddBlockPeer("Bad-Client_Normal|BlockListFromFile", peerIP, peerPort, torrentInfoHash) - return 1, peerNet - } - } } for port := range config.PortBlockList { @@ -234,26 +221,6 @@ func CheckPeer(peerIP string, peerPort int, peerID string, peerClient string, pe return 3, peerNet } } - for _, v := range ipBlockListFromURLCompiled { - if v == nil { - continue - } - if v.Contains(ip) { - Log("CheckPeer_AddBlockPeer (Bad-IP_FromURL)", "%s:%d %s|%s (TorrentInfoHash: %s)", true, peerIP, -1, strconv.QuoteToASCII(peerID), strconv.QuoteToASCII(peerClient), torrentInfoHash) - AddBlockPeer("Bad-IP_FromURL", peerIP, -1, torrentInfoHash) - return 3, peerNet - } - } - for _, v := range ipBlockListFromFileCompiled { - if v == nil { - continue - } - if v.Contains(ip) { - Log("CheckPeer_AddBlockPeer (Bad-IP_FromURL)", "%s:%d %s|%s (TorrentInfoHash: %s)", true, peerIP, -1, strconv.QuoteToASCII(peerID), strconv.QuoteToASCII(peerClient), torrentInfoHash) - AddBlockPeer("Bad-IP_FromURL", peerIP, -1, torrentInfoHash) - return 3, peerNet - } - } for _, v := range ipBlockCIDRMapFromSyncServerCompiled { if v == nil { continue diff --git a/sync.go b/sync.go new file mode 100644 index 0000000..214d539 --- /dev/null +++ b/sync.go @@ -0,0 +1,8 @@ +package main + +import ( + "sync" +) + +var Mutex = sync.Mutex{} +var WaitGroup = sync.WaitGroup{}