package uploader import ( "context" "crypto/md5" // #nosec G501 "encoding/hex" "github.com/go-faster/errors" "github.com/gotd/td/bin" "github.com/gotd/td/internal/crypto" "github.com/gotd/td/telegram/uploader/source" "github.com/gotd/td/tg" ) // Uploader is Telegram file uploader. type Uploader struct { rpc Client id func() (int64, error) partSize int pool *bin.Pool threads int progress Progress src source.Source } // NewUploader creates new Uploader. func NewUploader(rpc Client) *Uploader { return (&Uploader{ rpc: rpc, id: func() (int64, error) { return crypto.RandInt64(crypto.DefaultRand()) }, src: source.NewHTTPSource(), threads: 1, }).WithPartSize(defaultPartSize) } // WithProgress sets progress callback. func (u *Uploader) WithProgress(progress Progress) *Uploader { u.progress = progress return u } // WithSource sets URL resolver to use. func (u *Uploader) WithSource(src source.Source) *Uploader { u.src = src return u } // WithThreads sets uploading goroutines limit per upload. func (u *Uploader) WithThreads(threads int) *Uploader { if threads > 0 { u.threads = threads } return u } // WithIDGenerator sets id generator. func (u *Uploader) WithIDGenerator(cb func() (int64, error)) *Uploader { u.id = cb return u } // WithPartSize sets part size. // Should be divisible by 1024. // 524288 should be divisible by partSize. // // See https://core.telegram.org/api/files#uploading-files. func (u *Uploader) WithPartSize(partSize int) *Uploader { u.partSize = partSize u.pool = bin.NewPool(partSize) return u } // Upload uploads data from Upload object. func (u *Uploader) Upload(ctx context.Context, upload *Upload) (tg.InputFileClass, error) { if err := checkPartSize(u.partSize); err != nil { return nil, errors.Wrap(err, "invalid part size") } if err := u.initUpload(upload); err != nil { return nil, err } if !upload.big { return u.uploadSmall(ctx, upload) } return u.uploadBig(ctx, upload) } func (u *Uploader) uploadSmall(ctx context.Context, upload *Upload) (tg.InputFileClass, error) { h := md5.New() // #nosec G401 if err := u.smallLoop(ctx, h, upload); err != nil { return nil, err } return &tg.InputFile{ ID: upload.id, Parts: int(upload.sentParts.Load()), Name: upload.name, MD5Checksum: hex.EncodeToString(h.Sum(nil)), }, nil } func (u *Uploader) uploadBig(ctx context.Context, upload *Upload) (tg.InputFileClass, error) { if err := u.bigLoop(ctx, u.threads, upload); err != nil { return nil, err } return &tg.InputFileBig{ ID: upload.id, Parts: int(upload.sentParts.Load()), Name: upload.name, }, nil } func (u *Uploader) callback(ctx context.Context, state ProgressState) error { if u.progress != nil { return u.progress.Chunk(ctx, state) } return nil }