package downloader import ( "context" "sync" "github.com/go-faster/errors" "go.mau.fi/mautrix-telegram/pkg/gotd/tg" "go.mau.fi/mautrix-telegram/pkg/gotd/tgerr" ) type block struct { chunk offset int64 partSize int } // last compares partSize and chunk length to determine last part. func (b block) last() bool { // If returned chunk is smaller than requested part, it seems // it is last part. return len(b.data) < b.partSize } type reader struct { sch schema // immutable verifier *verifier // immutable partSize int // immutable offset int64 offsetMux sync.Mutex } func verifiedReader(sch schema, verifier *verifier) *reader { return &reader{ sch: sch, verifier: verifier, } } func plainReader(sch schema, partSize int) *reader { return &reader{ sch: sch, partSize: partSize, } } func (r *reader) Next(ctx context.Context) (block, error) { if r.verifier != nil { return r.nextHashed(ctx) } return r.nextPlain(ctx) } func (r *reader) nextHashed(ctx context.Context) (block, error) { // Fetch next hashes. hash, ok, err := r.verifier.next(ctx) if err != nil { return block{}, err } if !ok { return block{}, nil } // Get next chunk. b, err := r.next(ctx, hash.Offset, hash.Limit) if err != nil { return block{}, err } // Verify chunk. if !r.verifier.verify(hash, b.data) { return block{}, ErrHashMismatch } return b, nil } func (r *reader) nextPlain(ctx context.Context) (block, error) { r.offsetMux.Lock() offset := r.offset r.offset += int64(r.partSize) r.offsetMux.Unlock() return r.next(ctx, offset, r.partSize) } func (r *reader) next(ctx context.Context, offset int64, limit int) (block, error) { floodOrTimeoutRetries := 0 for { ch, err := r.sch.Chunk(ctx, offset, limit) if flood, err := tgerr.FloodWait(ctx, err); err != nil { if (flood || tgerr.Is(err, tg.ErrTimeout)) && floodOrTimeoutRetries < 10 { floodOrTimeoutRetries++ continue } return block{}, errors.Wrap(err, "get next chunk") } return block{ chunk: ch, offset: offset, partSize: r.partSize, }, nil } }