Skip to content
This repository has been archived by the owner on Nov 19, 2024. It is now read-only.

Emitting progress #128

Closed
eakarpov opened this issue Dec 12, 2018 · 4 comments
Closed

Emitting progress #128

eakarpov opened this issue Dec 12, 2018 · 4 comments

Comments

@eakarpov
Copy link

What would you like to have changed?

Emit percentage on archiving/unarchiving files to be able to count progress

Why is this feature a useful, necessary, and/or important addition to this project?

It allows to show the user a status of his files being archived/unarchived.

@jacokoo
Copy link

jacokoo commented Dec 27, 2018

And could there be a way to abort archive/unarchive programmatically?

@tomcruise81
Copy link

Here's an example for unarchiving using https://github.com/cheggaaa/pb

func createCountFilesWalker(accumulator *int) func(archiver.File) error {
	return func(f archiver.File) error {
		*accumulator++
		return nil
	}
}

func createExtractFilesWalker(bar *pb.ProgressBar, archiveHasBaseDir bool, outputDir string) func(archiver.File) error {
	pathSeparator := fmt.Sprintf("%c", os.PathSeparator)
	return func(f archiver.File) error {
		if app.ProgressMeters() {
			bar.Increment()
		}

		name := f.Name()
		switch h := f.Header.(type) {
		case zip.FileHeader:
			name = h.Name
		case *tar.Header:
			name = h.Name
		case *rardecode.FileHeader:
			name = h.Name
		default:
			return fmt.Errorf("Unable to process %v", h)
		}

		dirname := filepath.ToSlash(filepath.Join(outputDir, filepath.Dir(name)))
		if archiveHasBaseDir {
			namePathParts := strings.Split(filepath.Dir(name), pathSeparator)
			dirname = filepath.ToSlash(filepath.Join(outputDir, filepath.Join(namePathParts[1:]...)))
		}

		if f.IsDir() {
			log.Traceln("Making directory", dirname)
			os.MkdirAll(dirname, 0755)
			return nil
		}

		outFile := filepath.ToSlash(filepath.Join(dirname, filepath.Base(name)))
		log.Traceln("Writing file", outFile)
		data := make([]byte, f.Size())
		numBytesRead, err := f.Read(data)
		if err != nil && !(numBytesRead == int(f.Size()) && err == io.EOF) {
			return err
		}

		err = ioutil.WriteFile(outFile, data, 0644)
		return err
	}
}

// Unarchive a given inputFile to an outputDir
func Unarchive(inputFile string, outputDir string, archiveHasBaseDir bool) {
	inputFile = filepath.ToSlash(inputFile)
	outputDir = filepath.ToSlash(outputDir)

	if isDir, _ := IsDir(outputDir); isDir {
		err := os.RemoveAll(outputDir)
		if err != nil {
			log.Panic(err)
		}
	}

	log.Debugln("Extracting", inputFile, "to", outputDir)

	var archiveEntries = 0
	countFilesWalker := createCountFilesWalker(&archiveEntries)
	archiver.Walk(inputFile, countFilesWalker)
	log.Debugln(archiveEntries, " files/folders counted via walking")

	var bar *pb.ProgressBar
	if app.ProgressMeters() {
		// Create and start bar
		bar = pb.New(archiveEntries).SetRefreshRate(time.Millisecond * 20).Prefix(fmt.Sprintf("Extracting  %s ", filepath.Base(inputFile)))
		bar.ShowTimeLeft = false
		// bar.ShowSpeed = true
		bar.ShowFinalTime = false
		bar.Start()
	}

	extractFilesWalker := createExtractFilesWalker(bar, archiveHasBaseDir, outputDir)
	err := archiver.Walk(inputFile, extractFilesWalker)
	if err != nil {
		log.Panic(err)
	}

	if app.ProgressMeters() {
		bar.Finish()
	}

	log.Debugln("Successfully extracted", inputFile, "to", outputDir)
}

@JaskaranSM
Copy link

A ProgressCallback function would be better here (implemented in library). Something like https://godoc.org/github.com/itchio/sevenzip-go/sz#ExtractCallbackFuncs

@mholt
Copy link
Owner

mholt commented Jan 2, 2022

During the holidays I took some time and completely rewrote this library in #302 and am preparing to merge and tag v4. I gave this issue a lot of thought, and realized there's lots of ways to count progress and I'm not sure there's any one good answer. Fortunately, in v4, all the core APIs are stream-oriented/ so you can emit progress yourself. For example, to count bytes, I whipped up this gem:

type ProgressReader struct {
	io.Reader
	n int64
	N chan<- int64
}

func (pr *ProgressReader) Read(p []byte) (n int, err error) {
	n, err = pr.Reader.Read(p)
	if n > 0 {
		pr.n += int64(n)
		select {
		case pr.N <- pr.n:
		default:
		}
	}
	return
}

type ProgressWriter struct {
	io.Writer
	n int64
	N chan<- int64
}

func (pw *ProgressWriter) Write(p []byte) (n int, err error) {
	n, err = pw.Writer.Write(p)
	if n > 0 {
		pw.n += int64(n)
		select {
		case pw.N <- pw.n:
		default:
		}
	}
	return
}

Or you could count progress by files by using the FileHandler callbacks. Up to you!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants