package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"sync"
)

//并发下载器
//描述:编写一个程序,能同时从多个URL下载文件。每个下载任务用一个goroutine处理,使用sync.WaitGroup等待所有下载完成。
//技能点:goroutine、sync.WaitGroup、HTTP请求、错误处理。

var wg sync.WaitGroup
var uploadDir = "uploads"

func downloadFile(url string) {

	res, err := http.Get(url)
	if err != nil {
		log.Fatalln("创建请求失败:", err.Error())
	}
	defer res.Body.Close()
	if res.StatusCode != 200 {
		log.Fatalln("响应错误")
	}
	contentType := res.Header.Get("Content-Type")
	if contentType == "" {
		log.Fatalln("不存在content-type")
	}
	urlPath := res.Request.URL.String()
	lastIndex := strings.LastIndex(urlPath, "/")
	fileName := urlPath[lastIndex+1:]
	if os.MkdirAll(uploadDir, 0777) != nil {
		log.Fatal(err)
	}
	filePath := filepath.Join(uploadDir, fileName)

	fd, err := os.Create(filePath)
	if err != nil {
		log.Fatal(err)
	}
	defer fd.Close()
	_, err = io.Copy(fd, res.Body)
	if err != nil {
		log.Fatalln("写入文件失败: ", err.Error())
	}
	fmt.Println("文件保存在: ", filePath)

}

func worker(id int, tasks <-chan string) {
	defer wg.Done()
	for url := range tasks {
		downloadFile(url)
		log.Println("worker id: ", id, " download file successfully")
	}
}
func produce(tasks chan<- string, urls []string, times int) {
	for range times {
		for _, url := range urls {
			tasks <- url
		}
	}
	close(tasks)
}
func main() {
	arr := []string{
		"https://imgapi.xl0408.top/index.php",
		"https://www.dmoe.cc/random.php",
		"https://img.paulzzh.com/touhou/random",
	}
	tasks := make(chan string, 5)

	numWorkers := 20
	wg.Add(numWorkers)
	for i := 0; i < numWorkers; i++ {
		go worker(i, tasks)
	}
	go produce(tasks, arr, 100)
	wg.Wait()

}


package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"path"
	"strings"
	"sync"
	"time"
)

// 全局HTTP客户端(带连接池优化喵)
var client = &http.Client{
	Transport: &http.Transport{
		MaxIdleConns:        50,
		MaxIdleConnsPerHost: 10,
		IdleConnTimeout:     30 * time.Second,
	},
	Timeout: 30 * time.Second,
}

// 带进度条的Reader喵~
type ProgressReader struct {
	io.Reader
	Total      int64
	Downloaded int64
	OnProgress func(percent int)
}

func (pr *ProgressReader) Read(p []byte) (n int, err error) {
	n, err = pr.Reader.Read(p)
	pr.Downloaded += int64(n)
	if pr.OnProgress != nil && pr.Total > 0 {
		percent := int(float64(pr.Downloaded) / float64(pr.Total) * 100)
		pr.OnProgress(percent)
	}
	return
}

// 下载并保存图片的核心函数喵
func downloadAndSave(imgURL string, saveDir string) error {
	if _, err := os.Stat(saveDir); os.IsNotExist(err) {
		os.MkdirAll(saveDir, os.ModePerm)
	}
	// 1. 发起请求喵
	resp, err := client.Get(imgURL)
	if err != nil {
		return fmt.Errorf("请求失败喵: %v", err)
	}
	defer resp.Body.Close()

	// 2. 检查状态码喵
	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("收到奇怪的状态码喵: %d", resp.StatusCode)
	}

	url := resp.Request.URL.String()
	lastIndex := strings.LastIndex(url, "/")
	fileName := url[lastIndex+1:]

	filepath := path.Join(saveDir, fileName)

	// 4. 创建文件喵
	file, err := os.Create(filepath)
	if err != nil {
		return fmt.Errorf("创建文件失败喵: %v", err)
	}
	defer file.Close()

	// 5. 带进度条的下载喵~
	progressReader := &ProgressReader{
		Reader:     resp.Body,
		Total:      resp.ContentLength,
		OnProgress: func(percent int) { fmt.Printf("\r下载进度: %d%%", percent) },
	}

	if _, err := io.Copy(file, progressReader); err != nil {
		return fmt.Errorf("写入文件失败喵: %v", err)
	}
	fmt.Println() // 换行喵

	return nil
}

// Worker函数喵~
func downloadWorker(id int, jobs <-chan string, wg *sync.WaitGroup, saveDir string) {
	defer wg.Done()
	for imgURL := range jobs {
		fmt.Printf("[Worker%d] 开始处理 %s 喵~\n", id, imgURL)

		// 重试机制喵
		var err error
		for retry := 0; retry < 3; retry++ {
			err = downloadAndSave(imgURL, saveDir)
			if err == nil {
				fmt.Printf("[Worker%d] 下载成功喵!(◕‿◕✿)\n", id)
				break
			}
			fmt.Printf("[Worker%d] 第%d次重试喵...\n", id, retry+1)
			time.Sleep(1 * time.Second)
		}

		if err != nil {
			fmt.Printf("[Worker%d] 最终失败喵: %v\n", id, err)
		}
	}
}
func main() {
	jobs := make(chan string, 100) // 带缓冲通道喵
	var wg sync.WaitGroup

	// 先启动worker喵~
	for w := 1; w <= 5; w++ {
		wg.Add(1)
		go downloadWorker(w, jobs, &wg, "./images")
	}

	// 单独协程派发任务喵(摇尾巴)
	go func() {
		for i := 0; i < 100; i++ {
			url := "https://imgapi.xl0408.top/index.php"
			jobs <- url
			time.Sleep(100 * time.Millisecond) // 控制派发速度喵
		}
		close(jobs) // 重要喵!不然worker会永远阻塞喵!
	}()

	wg.Wait() // 主线程只需等待喵
	fmt.Println("所有任务完成喵!ヽ(✿゚▽゚)ノ")
}