cannot convert fs.FS zip file to io.ReadSeeker (missing Seek)

423 views
Skip to first unread message

Rory Campbell-Lange

unread,
Sep 21, 2022, 7:59:01 PM9/21/22
to golang-nuts
I have a program that needs to work equally for a directory of files or for the contents of a zip file, so I'm using an fs.FS to abstract the two.

Some of the files need to be provided to a PDF importer that accepts an io.ReadSeeker (#1).

Generally this is working fine except when trying to convert a zip file to an io.ReadSeeker. Concisely:

1 package main
2
3 import (
4 "archive/zip"
5 "fmt"
6 "io"
7 )
8
9 func main() {
10 z, _ := zip.OpenReader("a.zip")
11 a, _ := z.Open("a") // a is a file in a.zip
12 f, _ := a.Stat()
13 fmt.Println(f.Name() == "a") // true
14 fmt.Println(a.(io.ReadSeeker))
15 }

Fails on line 14 with

interface conversion: *zip.checksumReader is not io.ReadSeeker: missing method Seek

Advice on how to rectify this would be gratefully received.

#1
https://github.com/phpdave11/gofpdf/blob/b09d9214a2296c8ea10312d9c5a520e27f2148c9/contrib/gofpdi/gofpdi.go#L52

Dan Kortschak

unread,
Sep 21, 2022, 8:17:47 PM9/21/22
to golan...@googlegroups.com
On Thu, 2022-09-22 at 00:58 +0100, Rory Campbell-Lange wrote:
> interface conversion: *zip.checksumReader is not io.ReadSeeker:
> missing method Seek
>
> Advice on how to rectify this would be gratefully received.

Would it be acceptable to conditionally copy the reader's contents into
a buffer that implements io.ReadSeeker?

if rs, ok := r.(io.ReadSeeker); ok {
useReadSeeker(rs)
} else {
b, err := io.ReadAll(r)
// handle err
useReadSeeker(bytes.NewReader(b))
}


Anthony Martin

unread,
Sep 21, 2022, 8:20:05 PM9/21/22
to Rory Campbell-Lange, golang-nuts
Rory Campbell-Lange <ro...@campbell-lange.net> once said:
> interface conversion: *zip.checksumReader is not io.ReadSeeker:
> missing method Seek
>
> Advice on how to rectify this would be gratefully received.

Read the entire zip.File into memory with io.ReadAll and create
a bytes.Reader with the resulting byte slice. That will give you
a type that implements io.ReadSeeker.

Cheers,
Anthony

robert engels

unread,
Sep 21, 2022, 8:26:54 PM9/21/22
to Rory Campbell-Lange, golang-nuts
fs.FS does not support positional reads. It is always the entire file.

You can create a wrapper struct that implements the Seek() (trivial - just keep a position and adjust Read() accordingly).
> --
> You received this message because you are subscribed to the Google Groups "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/YyulHNPauBLiO%2BWa%40campbell-lange.net.

robert engels

unread,
Sep 21, 2022, 8:30:35 PM9/21/22
to golang-nuts
Others have suggested passing a ByteBuffer - I don’t think that will work because you will be missing other methods that are probably needed (FileInfo to get the name, etc)

Dan Kortschak

unread,
Sep 21, 2022, 8:56:55 PM9/21/22
to golan...@googlegroups.com
On Wed, 2022-09-21 at 19:30 -0500, robert engels wrote:
> Others have suggested passing a ByteBuffer - I don’t think that will
> work because you will be missing other methods that are probably
> needed (FileInfo to get the name, etc)

The function that was pointed to takes an ~io.ReadSeeker (oddly a
pointer to the interface) and doesn't look like it expects to
conditionally use other methods. The other details can always be
obtained from the original value.

robert engels

unread,
Sep 21, 2022, 9:15:52 PM9/21/22
to golang-nuts
Yea - my bad. Should be fine.
> --
> You received this message because you are subscribed to the Google Groups "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/f6d47a3fe5ae4425b51f978d8a5cda71aaa55cf3.camel%40kortschak.io.

Matthew Zimmerman

unread,
Sep 21, 2022, 10:47:59 PM9/21/22
to robert engels, golang-nuts
I've got this same challenge.  The contents of any file within a zip needs to be read as a stream, hence why there is no underlying seek method available (assuming to handle the decompression properly) So if you want to seek in a file within a zip, you need to read into a buffer first.  You don't have to load the whole zip, just the file within the zip you're working on.

Rory Campbell-Lange

unread,
Sep 22, 2022, 2:26:17 PM9/22/22
to Dan Kortschak, golan...@googlegroups.com
On 22/09/22, 'Dan Kortschak' via golang-nuts (golan...@googlegroups.com) wrote:
> On Thu, 2022-09-22 at 00:58 +0100, Rory Campbell-Lange wrote:
> > interface conversion: *zip.checksumReader is not io.ReadSeeker:
> > missing method Seek
>
> Would it be acceptable to conditionally copy the reader's contents into
> a buffer that implements io.ReadSeeker?
>
> if rs, ok := r.(io.ReadSeeker); ok {
> useReadSeeker(rs)
> } else {
> b, err := io.ReadAll(r)
> // handle err
> useReadSeeker(bytes.NewReader(b))
> }

Thanks very much for the suggestion. The following works ok as a test (which is basically your code):

fileAsReadSeeker := func(file fs.File) io.ReadSeeker {
if rs, ok := file.(io.ReadSeeker); ok {
return rs
}
b, err := io.ReadAll(file)
if err != nil {
panic(err) // to fix
}
return bytes.NewReader(b)
}

I'm now thinking of adding in a bytes.Buffer field into the underlying struct holding the fs.File file which can be initialised (and reused) if an io.ReadSeeker is required and provides a clearer error path.

Thanks again,
Rory
Reply all
Reply to author
Forward
0 new messages