Could you make your function accept some other interface? e.g.

type SizeReaderAt interface {
    io.ReaderAt
    Size() int64 
}

Values of this type will still be able to satisfy io.ReaderAt at point of 
use.

You could also use the existing io.SectionReader 
<https://pkg.go.dev/io#SectionReader> which has has Read(), ReadAt(), 
Seek() and Size() methods.

Another option is to define your own interface:
 
type StatReaderAt interface {
    io.ReaderAt
    Stat() (os.FileInfo, error) 
}

This interface also already satisfied if the caller is passing a concrete 
*os.File. However if they aren't, then the caller has a bit of work to wrap 
their value so that it carries a Stat() method which returns a value that 
satisfies FileInfo <https://pkg.go.dev/io/fs#FileInfo> (with 6 methods, 
although 5 of those could be dummies).

It's unfortunate that *os.File doesn't automatically satisfy 
io.SectionReader (since File doesn't have a Size() method). But I guess you 
could make a type which embeds *os.File and adds a Size() method which 
calls Stat().Size().

On Saturday, 1 April 2023 at 09:58:28 UTC+1 Jochen Voss wrote:

> Dear Bruno,
>
> Thanks for your answer.  Originally I had a separate size argument for the 
> function which opens a new reader, something like this:
>
>     func NewReader(data io.ReaderAt, size int64, opt *ReaderOptions) 
> (*Reader, error)
>
> But this makes it a bit annoying to create a reader for a file, because 
> you always need the additional Stat() call before, and also it looks a bit 
> weird, I though.  So my idea was to move the code for checking the size 
> into the function, and to simplify the API:
>
>     func NewReader(data io.ReaderAt, opt *ReaderOptions) (*Reader, error)
>
> This way the library does the work once, rather than every caller having 
> to do this separately.
>
> All the best,
> Jochen
>
> On Friday, 31 March 2023 at 21:38:04 UTC+1 Bruno Albuquerque wrote:
>
>> Not a direct answer to your question (your code looks like a reasonable 
>> implementation modulo any bugs I did not notice) but: Aren't you over 
>> engineering things? If the source is a PDF file, why not also pass the size 
>> to the function instead of doing all this work to get it? At some point you 
>> have to open the file, right? And checking the size of a file is trivial 
>> (it is just a stat call) and then you do not need to do that.
>>
>>
>> On Fri, Mar 31, 2023 at 4:30 PM Jochen Voss <joche...@gmail.com> wrote:
>>
>>> Dear all,
>>>
>>> I am trying to get the size of an io.ReaderAt, i.e. the offset after 
>>> which no more data can be read.  I have some working (?) code (
>>> https://go.dev/play/p/wTouYbaJ7RG , also reproduced below), but I am 
>>> not sure whether what I do is correct and the best way to do this.  Some 
>>> questions:
>>>
>>>    - Does the standard library provide a way to get the size of an 
>>>    io,ReaderAt?
>>>    - Is my code correct?
>>>    - Is there a better way to do this?
>>>    - If a type provides not only a ReadAt() method, but also a Size() 
>>>    method, would it be save to assume that Size() returns how many bytes 
>>> are 
>>>    accessible via ReadAt()?  This seems to work for bytes.Reader and 
>>>    strings.Reader,  but there may be types out there where Size() does 
>>>    something different?
>>>    - If ReadAt(p, x) returns io.EOF for an offset x, is it then 
>>>    guaranteed that then ReadAt(p, y) also returns io.EOF for all y > 
>>>    x?  Or could there be different error messages, or "files with holes", 
>>> or 
>>>    whatnot?
>>>    - Which types in the standard library provide ReadAt methods?  I 
>>>    know of os.File, strings.Reader, and bytes.Reader.  Any others?
>>>
>>> For context: this is for reading the cross reference table of PDF files, 
>>> which have to be located by following some convoluted route starting *from 
>>> the end* of the PDF file.
>>>
>>> Many thanks,
>>> Jochen
>>>
>>> func getSize(r io.ReaderAt) (int64, error) {
>>> if f, ok := r.(*os.File); ok {
>>> fi, err := f.Stat()
>>> if err != nil {
>>> return 0, err
>>> }
>>> return fi.Size(), nil
>>> }
>>> if b, ok := r.(*bytes.Reader); ok {
>>> return int64(b.Size()), nil
>>> }
>>> if s, ok := r.(*strings.Reader); ok {
>>> return int64(s.Size()), nil
>>> }
>>>
>>> buf := make([]byte, 1024)
>>> n, err := r.ReadAt(buf, 0)
>>> if err == io.EOF {
>>> return int64(n), nil
>>> } else if err != nil {
>>> return 0, err
>>> }
>>>
>>> lowerBound := int64(n) // all bytes before lowerBound are known to be 
>>> present
>>> var upperBound int64   // at least one byte before upperBound is known 
>>> to be missing
>>> for {
>>> test := 2 * lowerBound
>>> _, err := r.ReadAt(buf[:1], test-1)
>>> if err == io.EOF {
>>> upperBound = test
>>> break
>>> } else if err != nil {
>>> return 0, err
>>> }
>>> lowerBound = test
>>> }
>>>
>>> for lowerBound+1 < upperBound {
>>> test := (lowerBound + upperBound + 1) / 2
>>> _, err := r.ReadAt(buf[:1], test-1)
>>> if err == io.EOF {
>>> upperBound = test
>>> } else if err != nil {
>>> return 0, err
>>> } else {
>>> lowerBound = test
>>> }
>>> }
>>> return lowerBound, nil
>>> }
>>>
>>> -- 
>>> 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/aa604ac0-3542-4a9e-adef-a9eaecfd8170n%40googlegroups.com
>>>  
>>> <https://groups.google.com/d/msgid/golang-nuts/aa604ac0-3542-4a9e-adef-a9eaecfd8170n%40googlegroups.com?utm_medium=email&utm_source=footer>
>>> .
>>>
>>

-- 
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+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/181ceb1e-9afe-4f92-b7b6-f739024dfd53n%40googlegroups.com.

Reply via email to