diff --git a/.github/workflows/test-linux.yml b/.github/workflows/test-linux.yml index 6456ddb8..4b532e13 100644 --- a/.github/workflows/test-linux.yml +++ b/.github/workflows/test-linux.yml @@ -21,7 +21,7 @@ jobs: - uses: actions/setup-go@v4 with: - go-version: 1.18 + go-version: 1.20.1 - uses: actions/checkout@v3 diff --git a/browser_test.go b/browser_test.go index 1337317d..6c20f5d7 100644 --- a/browser_test.go +++ b/browser_test.go @@ -285,7 +285,7 @@ func TestBinarySize(t *testing.T) { stat, err := os.Stat("tmp/translator") g.E(err) - g.Lte(float64(stat.Size())/1024/1024, 10.6) // mb + g.Lte(float64(stat.Size())/1024/1024, 11) // mb } func TestBrowserCookies(t *testing.T) { diff --git a/cspell.json b/cspell.json index 8173b26c..1cb58cf6 100644 --- a/cspell.json +++ b/cspell.json @@ -38,6 +38,7 @@ "enctype", "evenodd", "excludesfile", + "fetchup", "fontconfig", "Fullscreen", "Geolocation", @@ -97,12 +98,12 @@ "srgb", "staticcheck", "stdlib", + "termux", "tlid", "touchend", "touchstart", "tracebackancestors", "trimpath", - "termux", "Typedarray", "tzdata", "Unserializable", diff --git a/go.mod b/go.mod index b85b2510..8772683d 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,9 @@ module github.com/go-rod/rod go 1.16 require ( + github.com/ysmood/fetchup v0.2.1 github.com/ysmood/goob v0.4.0 - github.com/ysmood/got v0.33.0 + github.com/ysmood/got v0.33.2 github.com/ysmood/gotrace v0.6.0 github.com/ysmood/gson v0.7.3 github.com/ysmood/leakless v0.8.0 diff --git a/go.sum b/go.sum index dfc3e48f..e38ff9ab 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,12 @@ +github.com/ysmood/fetchup v0.2.1 h1:n/NgIx92KOXFiKAhK3d+LlKpl8JuSjh5U27ULmHKtag= +github.com/ysmood/fetchup v0.2.1/go.mod h1:94ROLWpn5fmCD4LPlcZ+LOE/iE/kRTU3kL+0ue/V+Os= github.com/ysmood/goob v0.4.0 h1:HsxXhyLBeGzWXnqVKtmT9qM7EuVs/XOgkX7T6r1o1AQ= github.com/ysmood/goob v0.4.0/go.mod h1:u6yx7ZhS4Exf2MwciFr6nIM8knHQIE22lFpWHnfql18= github.com/ysmood/gop v0.0.1 h1:EGxcy28xzMfjT+TlLf8JQd1w/sTz0XPrbHazLLs84BA= github.com/ysmood/gop v0.0.1/go.mod h1:JenWOSCGucsXYHdw1Cw4a0fmLnux7jXBDdlBGjh8V/g= github.com/ysmood/got v0.32.0/go.mod h1:pE1l4LOwOBhQg6A/8IAatkGp7uZjnalzrZolnlhhMgY= -github.com/ysmood/got v0.33.0 h1:E0XJ5/mI2wdCCjXkqJ2d96GdOEYFwe2ou0FqHJf6zr4= -github.com/ysmood/got v0.33.0/go.mod h1:P3C/Wwttv4uq/tpovaH+c8ANmHePyFPxEbNzdxcEGDU= +github.com/ysmood/got v0.33.2 h1:mz0PaCMzR//YBtDDkDf6z0O09SfotXBHzw3zLrrS2sw= +github.com/ysmood/got v0.33.2/go.mod h1:P3C/Wwttv4uq/tpovaH+c8ANmHePyFPxEbNzdxcEGDU= github.com/ysmood/gotrace v0.6.0 h1:SyI1d4jclswLhg7SWTL6os3L1WOKeNn/ZtzVQF8QmdY= github.com/ysmood/gotrace v0.6.0/go.mod h1:TzhIG7nHDry5//eYZDYcTzuJLYQIkykJzCRIo4/dzQM= github.com/ysmood/gson v0.7.3 h1:QFkWbTH8MxyUTKPkVWAENJhxqdBa4lYTQWqZCiLG6kE= diff --git a/go.work.sum b/go.work.sum index dfc3e48f..c5ed75a6 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,13 +1,4 @@ -github.com/ysmood/goob v0.4.0 h1:HsxXhyLBeGzWXnqVKtmT9qM7EuVs/XOgkX7T6r1o1AQ= -github.com/ysmood/goob v0.4.0/go.mod h1:u6yx7ZhS4Exf2MwciFr6nIM8knHQIE22lFpWHnfql18= -github.com/ysmood/gop v0.0.1 h1:EGxcy28xzMfjT+TlLf8JQd1w/sTz0XPrbHazLLs84BA= -github.com/ysmood/gop v0.0.1/go.mod h1:JenWOSCGucsXYHdw1Cw4a0fmLnux7jXBDdlBGjh8V/g= -github.com/ysmood/got v0.32.0/go.mod h1:pE1l4LOwOBhQg6A/8IAatkGp7uZjnalzrZolnlhhMgY= -github.com/ysmood/got v0.33.0 h1:E0XJ5/mI2wdCCjXkqJ2d96GdOEYFwe2ou0FqHJf6zr4= -github.com/ysmood/got v0.33.0/go.mod h1:P3C/Wwttv4uq/tpovaH+c8ANmHePyFPxEbNzdxcEGDU= -github.com/ysmood/gotrace v0.6.0 h1:SyI1d4jclswLhg7SWTL6os3L1WOKeNn/ZtzVQF8QmdY= -github.com/ysmood/gotrace v0.6.0/go.mod h1:TzhIG7nHDry5//eYZDYcTzuJLYQIkykJzCRIo4/dzQM= -github.com/ysmood/gson v0.7.3 h1:QFkWbTH8MxyUTKPkVWAENJhxqdBa4lYTQWqZCiLG6kE= -github.com/ysmood/gson v0.7.3/go.mod h1:3Kzs5zDl21g5F/BlLTNcuAGAYLKt2lV5G8D1zF3RNmg= -github.com/ysmood/leakless v0.8.0 h1:BzLrVoiwxikpgEQR0Lk8NyBN5Cit2b1z+u0mgL4ZJak= -github.com/ysmood/leakless v0.8.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ= +github.com/ysmood/fetchup v0.2.1 h1:n/NgIx92KOXFiKAhK3d+LlKpl8JuSjh5U27ULmHKtag= +github.com/ysmood/fetchup v0.2.1/go.mod h1:94ROLWpn5fmCD4LPlcZ+LOE/iE/kRTU3kL+0ue/V+Os= +github.com/ysmood/got v0.33.2 h1:mz0PaCMzR//YBtDDkDf6z0O09SfotXBHzw3zLrrS2sw= +github.com/ysmood/got v0.33.2/go.mod h1:P3C/Wwttv4uq/tpovaH+c8ANmHePyFPxEbNzdxcEGDU= diff --git a/lib/launcher/browser.go b/lib/launcher/browser.go index e9b332ce..122bb95a 100644 --- a/lib/launcher/browser.go +++ b/lib/launcher/browser.go @@ -3,24 +3,19 @@ package launcher import ( "bytes" "context" - "crypto/tls" "errors" "fmt" - "io" - "io/ioutil" "log" "net/http" - "net/url" "os" "os/exec" "path/filepath" "runtime" - "strconv" "strings" - "sync" "github.com/go-rod/rod/lib/defaults" "github.com/go-rod/rod/lib/utils" + "github.com/ysmood/fetchup" "github.com/ysmood/leakless" ) @@ -88,8 +83,8 @@ type Browser struct { // Revision of the browser to use Revision int - // Dir to download browser. - Dir string + // RootDir to download different browser versions. + RootDir string // Log to print output Logger utils.Logger @@ -97,11 +92,8 @@ type Browser struct { // LockPort a tcp port to prevent race downloading. Default is 2968 . LockPort int - // Proxy to use (http/socks5). Default is nil. - proxyURL *url.URL - - // IgnoreCerts skips proxy certificate validation - IgnoreCerts bool + // HTTPClient to download the browser + HTTPClient *http.Client } // NewBrowser with default values @@ -110,155 +102,50 @@ func NewBrowser() *Browser { Context: context.Background(), Revision: RevisionDefault, Hosts: []Host{HostGoogle, HostNPM, HostPlaywright}, - Dir: DefaultBrowserDir, + RootDir: DefaultBrowserDir, Logger: log.New(os.Stdout, "[launcher.Browser]", log.LstdFlags), LockPort: defaults.LockPort, } } -// Destination of the downloaded browser executable -func (lc *Browser) Destination() string { +// Dir to download the browser +func (lc *Browser) Dir() string { + return filepath.Join(lc.RootDir, fmt.Sprintf("chromium-%d", lc.Revision)) +} + +// BinPath to download the browser executable +func (lc *Browser) BinPath() string { bin := map[string]string{ - "darwin": fmt.Sprintf("chromium-%d/chrome-mac/Chromium.app/Contents/MacOS/Chromium", lc.Revision), - "linux": fmt.Sprintf("chromium-%d/chrome-linux/chrome", lc.Revision), - "windows": fmt.Sprintf("chromium-%d/chrome-win/chrome.exe", lc.Revision), + "darwin": "Chromium.app/Contents/MacOS/Chromium", + "linux": "chrome", + "windows": "chrome.exe", }[runtime.GOOS] - return filepath.Join(lc.Dir, bin) + return filepath.Join(lc.Dir(), filepath.FromSlash(bin)) } // Download browser from the fastest host. It will race downloading a TCP packet from each host and use the fastest host. -func (lc *Browser) Download() (err error) { - defer func() { - if e := recover(); e != nil { - err = e.(error) - } - }() - - u, err := lc.fastestHost() - utils.E(err) - - if u == "" { - panic(fmt.Errorf("Can't find a browser binary for your OS, the doc might help https://go-rod.github.io/#/compatibility?id=os")) - } - - return lc.download(lc.Context, u) -} - -// Proxy sets the proxy for chrome download -func (lc *Browser) Proxy(URL string) error { - proxyURL, err := url.Parse(URL) - if err != nil { - return err - } - lc.proxyURL = proxyURL - return err -} - -func (lc *Browser) fastestHost() (fastest string, err error) { - lc.Logger.Println("try to find the fastest host to download the browser binary") - - setURL := sync.Once{} - ctx, cancel := context.WithCancel(lc.Context) - defer cancel() - - wg := sync.WaitGroup{} +func (lc *Browser) Download() error { + us := []string{} for _, host := range lc.Hosts { - u := host(lc.Revision) - - lc.Logger.Println("check", u) - wg.Add(1) - - go func() { - defer func() { - err := recover() - if err != nil { - lc.Logger.Println("check result:", err) - } - wg.Done() - }() - - q, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil) - utils.E(err) - - res, err := lc.httpClient().Do(q) - utils.E(err) - defer func() { _ = res.Body.Close() }() - - if res.StatusCode == http.StatusOK { - buf := make([]byte, 64*1024) // a TCP packet won't be larger than 64KB - _, err = res.Body.Read(buf) - utils.E(err) - - setURL.Do(func() { - fastest = u - cancel() - }) - } - }() + us = append(us, host(lc.Revision)) } - wg.Wait() - - return -} - -func (lc *Browser) download(ctx context.Context, u string) error { - lc.Logger.Println("Download:", u) - zipPath := filepath.Join(lc.Dir, fmt.Sprintf("chromium-%d.zip", lc.Revision)) + dir := lc.Dir() - err := utils.Mkdir(lc.Dir) - utils.E(err) - - zipFile, err := os.Create(zipPath) - utils.E(err) - - q, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil) - utils.E(err) - - res, err := lc.httpClient().Do(q) - utils.E(err) - defer func() { _ = res.Body.Close() }() - - size, _ := strconv.ParseInt(res.Header.Get("Content-Length"), 10, 64) - - if res.StatusCode >= 400 || size < 1024*1024 { - b, err := ioutil.ReadAll(res.Body) - utils.E(err) - err = errors.New("failed to download the browser") - return fmt.Errorf("%w: %d %s", err, res.StatusCode, string(b)) + fu := fetchup.New(dir, us...) + fu.Ctx = lc.Context + fu.Logger = lc.Logger + if lc.HTTPClient != nil { + fu.HttpClient = lc.HTTPClient } - progress := &progresser{ - size: int(size), - logger: lc.Logger, + err := fu.Fetch() + if err != nil { + return fmt.Errorf("Can't find a browser binary for your OS, the doc might help https://go-rod.github.io/#/compatibility?id=os : %w", err) } - _, err = io.Copy(io.MultiWriter(progress, zipFile), res.Body) - utils.E(err) - - err = zipFile.Close() - utils.E(err) - - unzipPath := filepath.Join(lc.Dir, fmt.Sprintf("chromium-%d", lc.Revision)) - _ = os.RemoveAll(unzipPath) - utils.E(unzip(lc.Logger, zipPath, unzipPath)) - return os.Remove(zipPath) -} - -func (lc *Browser) httpClient() *http.Client { - transport := &http.Transport{ - DisableKeepAlives: true, - } - if lc.IgnoreCerts { - transport.TLSClientConfig = &tls.Config{ - InsecureSkipVerify: true, - } - } - if lc.proxyURL != nil { - transport.Proxy = http.ProxyURL(lc.proxyURL) - } - return &http.Client{Transport: transport} + return fetchup.StripFirstDir(dir) } // Get is a smart helper to get the browser executable path. @@ -267,10 +154,13 @@ func (lc *Browser) Get() (string, error) { defer leakless.LockPort(lc.LockPort)() if lc.Validate() == nil { - return lc.Destination(), nil + return lc.BinPath(), nil } - return lc.Destination(), lc.Download() + // Try to cleanup before downloading + _ = os.RemoveAll(lc.Dir()) + + return lc.BinPath(), lc.Download() } // MustGet is similar with Get @@ -283,12 +173,12 @@ func (lc *Browser) MustGet() string { // Validate returns nil if the browser executable valid. // If the executable is malformed it will return error. func (lc *Browser) Validate() error { - _, err := os.Stat(lc.Destination()) + _, err := os.Stat(lc.BinPath()) if err != nil { return err } - cmd := exec.Command(lc.Destination(), "--headless", "--no-sandbox", + cmd := exec.Command(lc.BinPath(), "--headless", "--no-sandbox", "--disable-gpu", "--dump-dom", "about:blank") b, err := cmd.CombinedOutput() if err != nil { diff --git a/lib/launcher/launcher.go b/lib/launcher/launcher.go index 63b14970..162eed3c 100644 --- a/lib/launcher/launcher.go +++ b/lib/launcher/launcher.go @@ -303,7 +303,6 @@ func (l *Launcher) RemoteDebuggingPort(port int) *Launcher { // Proxy for the browser func (l *Launcher) Proxy(host string) *Launcher { - _ = l.browser.Proxy(host) return l.Set(flags.ProxyServer, host) } diff --git a/lib/launcher/launcher_test.go b/lib/launcher/launcher_test.go index ff3b58a3..06ce7a53 100644 --- a/lib/launcher/launcher_test.go +++ b/lib/launcher/launcher_test.go @@ -3,24 +3,18 @@ package launcher_test import ( "archive/zip" "bytes" - "context" "crypto" "crypto/x509" "encoding/pem" "flag" - "fmt" - "io" "io/ioutil" "net/http" - "net/http/httptest" - "net/http/httputil" "net/url" "os" "os/exec" "path/filepath" "strings" "testing" - "time" "github.com/go-rod/rod/lib/defaults" "github.com/go-rod/rod/lib/launcher" @@ -40,74 +34,29 @@ func TestDownloadHosts(t *testing.T) { } func TestDownload(t *testing.T) { - g := setup(t) + g := got.T(t) + + buf := bytes.NewBuffer(nil) + z := zip.NewWriter(buf) + f, _ := z.Create(filepath.FromSlash("a/b/c.txt")) + _, _ = f.Write([]byte(g.RandStr(500 * 1024))) + _ = z.Close() s := g.Serve() - s.Mux.HandleFunc("/fast/", func(rw http.ResponseWriter, r *http.Request) { - buf := bytes.NewBuffer(nil) - zw := zip.NewWriter(buf) - - // folder "to" - h := &zip.FileHeader{Name: "to/"} - h.SetMode(0755) - _, err := zw.CreateHeader(h) - g.E(err) - - // file "file.txt" - w, err := zw.CreateHeader(&zip.FileHeader{Name: "to/file.txt"}) - g.E(err) - b := []byte(g.RandStr(2 * 1024 * 1024)) - g.E(w.Write(b)) - - g.E(zw.Close()) - - rw.Header().Add("Content-Length", fmt.Sprintf("%d", buf.Len())) - _, _ = io.Copy(rw, buf) - }) - s.Mux.HandleFunc("/slow/", func(rw http.ResponseWriter, r *http.Request) { - t := time.NewTimer(3 * time.Second) - select { - case <-t.C: - case <-r.Context().Done(): - t.Stop() - } - }) + s.Route("/", ".zip", buf.Bytes()) - b, cancel := newBrowser() + b := launcher.NewBrowser() + b.Revision = 1 b.Logger = utils.LoggerQuiet - defer cancel() - - b.Hosts = []launcher.Host{launcher.HostTest(s.URL("/slow")), launcher.HostTest(s.URL("/fast"))} - b.Dir = filepath.Join("tmp", "browser-from-mirror", g.RandStr(16)) - g.E(b.Download()) - g.Nil(os.Stat(b.Dir)) - - // download chrome with a proxy - // should fail with self signed certificate - p := httptest.NewTLSServer(&httputil.ReverseProxy{Director: func(_ *http.Request) {}}) - defer p.Close() - // invalid proxy URL should trigger an error - err := b.Proxy(`invalid.escaping%%2`) - g.Eq(err.Error(), `parse "invalid.escaping%%2": invalid URL escape "%%2"`) - - g.E(b.Proxy(p.URL)) - g.NotNil(b.Download()) - // should instead be successful with ignore certificate - b.IgnoreCerts = true - g.E(b.Download()) - g.Nil(os.Stat(b.Dir)) -} + b.Hosts = []launcher.Host{func(_ int) string { + return s.URL("/a.zip") + }} -func TestBrowserGet(t *testing.T) { - g := setup(t) + g.Cleanup(func() { _ = os.RemoveAll(b.Dir()) }) - g.Nil(os.Stat(launcher.NewBrowser().MustGet())) + b.MustGet() - b := launcher.NewBrowser() - b.Revision = 0 - b.Logger = utils.LoggerQuiet - _, err := b.Get() - g.Eq(err.Error(), "Can't find a browser binary for your OS, the doc might help https://go-rod.github.io/#/compatibility?id=os") + g.PathExists(b.Dir()) } func TestLaunch(t *testing.T) { @@ -239,16 +188,6 @@ func TestLaunchErr(t *testing.T) { } } -func newBrowser() (*launcher.Browser, func()) { - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) - b := launcher.NewBrowser() - if !testing.Verbose() { - b.Logger = utils.LoggerQuiet - } - b.Context = ctx - return b, cancel -} - var testProfileDir = flag.Bool("test-profile-dir", false, "set it to test profile dir") func TestProfileDir(t *testing.T) { @@ -277,16 +216,16 @@ func TestBrowserValid(t *testing.T) { b.Revision = 0 g.Err(b.Validate()) - g.E(utils.Mkdir(filepath.Dir(b.Destination()))) - g.Cleanup(func() { _ = os.RemoveAll(b.Destination()) }) + g.E(utils.Mkdir(filepath.Dir(b.BinPath()))) + g.Cleanup(func() { _ = os.RemoveAll(b.Dir()) }) - g.E(exec.Command("go", "build", "-o", b.Destination(), "./fixtures/chrome-exit-err").CombinedOutput()) + g.E(exec.Command("go", "build", "-o", b.BinPath(), "./fixtures/chrome-exit-err").CombinedOutput()) g.Has(b.Validate().Error(), "failed to run the browser") - g.E(exec.Command("go", "build", "-o", b.Destination(), "./fixtures/chrome-empty").CombinedOutput()) + g.E(exec.Command("go", "build", "-o", b.BinPath(), "./fixtures/chrome-empty").CombinedOutput()) g.Eq(b.Validate().Error(), "the browser executable doesn't support headless mode") - g.E(exec.Command("go", "build", "-o", b.Destination(), "./fixtures/chrome-lib-missing").CombinedOutput()) + g.E(exec.Command("go", "build", "-o", b.BinPath(), "./fixtures/chrome-lib-missing").CombinedOutput()) g.Nil(b.Validate()) } @@ -354,9 +293,20 @@ func TestIgnoreCerts_InvalidCert(t *testing.T) { } } -func TestIgnoreCerts_BrowserProxySkipValidation(t *testing.T) { +func TestBrowserDownloadErr(t *testing.T) { g := setup(t) b := launcher.NewBrowser() - // certificate validation skip is disabled by default - g.False(b.IgnoreCerts) + b.Logger = utils.LoggerQuiet + b.HTTPClient = http.DefaultClient + b.Hosts = []launcher.Host{} + g.Err(b.Download()) + + s := g.Serve() + s.Route("/download", ".txt", "ok") + + b = launcher.NewBrowser() + b.Hosts = []launcher.Host{func(_ int) string { + return s.URL("/download/file") + }} + g.Err(b.Download()) } diff --git a/lib/launcher/private_test.go b/lib/launcher/private_test.go index 216a7ea8..ce1dd3e7 100644 --- a/lib/launcher/private_test.go +++ b/lib/launcher/private_test.go @@ -31,12 +31,6 @@ func HostTest(host string) Host { var setup = got.Setup(nil) -func TestMain(m *testing.M) { - NewBrowser().MustGet() // preload browser to local - - os.Exit(m.Run()) -} - func TestToHTTP(t *testing.T) { g := setup(t) @@ -57,12 +51,6 @@ func TestToWS(t *testing.T) { g.Eq("ws", toWS(*u).Scheme) } -func TestUnzip(t *testing.T) { - g := setup(t) - - g.Err(unzip(utils.LoggerQuiet, "", "")) -} - func TestLaunchOptions(t *testing.T) { g := setup(t) @@ -144,22 +132,12 @@ func TestLaunchErrs(t *testing.T) { s.Route("/", "", nil) l = New().Bin("") l.browser.Logger = utils.LoggerQuiet - l.browser.Dir = filepath.Join("tmp", "browser-from-mirror", g.RandStr(16)) + l.browser.RootDir = filepath.Join("tmp", "browser-from-mirror", g.RandStr(16)) l.browser.Hosts = []Host{HostTest(s.URL())} _, err = l.Launch() g.Err(err) } -func TestProgresser(t *testing.T) { - g := setup(t) - - p := progresser{size: 100, logger: utils.LoggerQuiet} - - g.E(p.Write(make([]byte, 100))) - g.E(p.Write(make([]byte, 100))) - g.E(p.Write(make([]byte, 100))) -} - func TestURLParserErr(t *testing.T) { g := setup(t) @@ -174,15 +152,6 @@ func TestURLParserErr(t *testing.T) { g.Eq(u.Err().Error(), "[launcher] Failed to launch the browser, the doc might help https://go-rod.github.io/#/compatibility?id=os: /tmp/rod/chromium-818858/chrome-linux/chrome: error while loading shared libraries: libgobject-2.0.so.0: cannot open shared object file: No such file or directory") } -func TestBrowserDownloadErr(t *testing.T) { - g := setup(t) - - r := g.Serve().Route("/", "", "") - b := NewBrowser() - b.Logger = utils.LoggerQuiet - g.Has(b.download(g.Context(), r.URL()).Error(), "failed to download the browser: 200") -} - func TestTestOpen(_ *testing.T) { openExec = func(name string, arg ...string) *exec.Cmd { cmd := exec.Command("not-exists") diff --git a/lib/launcher/utils.go b/lib/launcher/utils.go index a2d80cb5..421d98aa 100644 --- a/lib/launcher/utils.go +++ b/lib/launcher/utils.go @@ -1,54 +1,18 @@ package launcher import ( - "archive/zip" "crypto" "crypto/sha256" "crypto/x509" "encoding/base64" "fmt" - "io" "net/url" - "os" - "path/filepath" - "time" "github.com/go-rod/rod/lib/utils" ) var inContainer = utils.InContainer -type progresser struct { - size int - count int - logger utils.Logger - last time.Time -} - -func (p *progresser) Write(b []byte) (n int, err error) { - n = len(b) - - if p.count == 0 { - p.logger.Println("Progress:") - } - - p.count += n - - if p.count == p.size { - p.logger.Println("100%") - return - } - - if time.Since(p.last) < time.Second { - return - } - - p.last = time.Now() - p.logger.Println(fmt.Sprintf("%02d%%", p.count*100/p.size)) - - return -} - func toHTTP(u url.URL) *url.URL { newURL := u if newURL.Scheme == "ws" { @@ -69,52 +33,6 @@ func toWS(u url.URL) *url.URL { return &newURL } -func unzip(logger utils.Logger, from, to string) (err error) { - defer func() { - if e := recover(); e != nil { - err = e.(error) - } - }() - - logger.Println("Unzip to:", to) - - zr, err := zip.OpenReader(from) - utils.E(err) - - size := 0 - for _, f := range zr.File { - size += int(f.FileInfo().Size()) - } - - progress := &progresser{size: size, logger: logger} - - for _, f := range zr.File { - p := filepath.Join(to, f.Name) - - _ = utils.Mkdir(filepath.Dir(p)) - - if f.FileInfo().IsDir() { - err := os.Mkdir(p, f.Mode()) - utils.E(err) - continue - } - - r, err := f.Open() - utils.E(err) - - dst, err := os.OpenFile(p, os.O_RDWR|os.O_CREATE|os.O_TRUNC, f.Mode()) - utils.E(err) - - _, err = io.Copy(io.MultiWriter(dst, progress), r) - utils.E(err) - - err = dst.Close() - utils.E(err) - } - - return zr.Close() -} - // certSPKI generates the SPKI of a certificate public key // https://blog.afoolishmanifesto.com/posts/golang-self-signed-and-pinned-certs/ func certSPKI(pk crypto.PublicKey) ([]byte, error) {