Roshan
Roshan

Reputation: 513

How to extract .7z files in Go

I have a 7z archive of a number of .txt files. I am trying to list all the files in the archive and upload them to an s3 bucket. But I'm having trouble with extracting .7z archives on Go. To do this, I found a package github.com/gen2brain/go-unarr (imported as extractor) and this is what I have so far

        content, err := ioutil.ReadFile("sample_archive.7z")
        if err != nil {
            fmt.Printf("err: %+v", err)
        }

        a, err := extractor.NewArchiveFromMemory(content)
        if err != nil {
            fmt.Printf("err: %+v", err)
        }

        lst, _ := a.List()
        fmt.Printf("lst: %+v", last)

This prints a list of all the files in the archive. But this has two issues.

It reads files from local using ioutil and the input of NewArchiveFromMemory must be of type []byte. But I can't read from local and will have to use a file from memory of type os.file. So I will either have to find a different method or convert the os.file to []byte. There's another method NewArchiveFromReader(r io.Reader). But this is returning an error saying Bad File Descriptor.

file, err := os.OpenFile(
    path,
    os.O_WRONLY|os.O_TRUNC|os.O_CREATE,
    0666,
)

a, err := extractor.NewArchiveFromReader(file)
if err != nil {
    fmt.Printf("ERROR: %+v", err)
}
    
lst, _ := a.List()
fmt.Printf("files: %+v\n", lst)

I am able to get the list of the files in the archive. And using Extract(destinaltion_path string), I can also extract it to a local directory. But I want the extracted files also in os.file format ( ie. a list of os.file since there will be multiple files ).

How can I change my current code to achieve both the above targets? Is there any other library to do this?

Upvotes: 1

Views: 2330

Answers (1)

Robert Nubel
Robert Nubel

Reputation: 7532

  1. os.File implements the io.Reader interface (because it has a Read([]byte) (int, error) method defined), so you can use NewArchiveFromReader(file) without any conversions needed. You can read up on Go interfaces for more background on why that works.
  2. If you're okay with extracting to a local directory, you can do that and then read the files back in (warning, may contain typos):
func extractAndOpenAll(*extractor.Archive) ([]*os.File, error) {
  err := a.Extract("/tmp/path") // consider using ioutil.TempDir()
  if err != nil {
    return nil, err
  }

  filestats, err := ioutil.ReadDir("/tmp/path")
  if err != nil {
    return nil, err
  }

  # warning: all these file handles must be closed by the caller, 
  # which is why even the error case here returns the list of files.
  # if you forget, your process might leak file handles. 
  files := make([]*os.File, 0)
  for _, fs := range(filestats) {
    file, err := os.Open(fs.Name())
    if err != nil {
      return files, err
    }

    files = append(files, file)
  }

  return files, nil
}

It is possible to use the archived files without writing back to disk (https://github.com/gen2brain/go-unarr#read-all-entries-from-archive), but whether or not you should do that instead depends on what your next step is.

Upvotes: 2

Related Questions