mirror of
https://github.com/iyear/tdl
synced 2025-01-08 11:57:55 +08:00
refactor(up): extract to interface
This commit is contained in:
parent
98233e1de4
commit
5233607250
52
app/up/elem.go
Normal file
52
app/up/elem.go
Normal file
@ -0,0 +1,52 @@
|
||||
package up
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/gotd/td/telegram/peers"
|
||||
"github.com/gotd/td/tg"
|
||||
|
||||
"github.com/iyear/tdl/pkg/uploader"
|
||||
)
|
||||
|
||||
type iterElem struct {
|
||||
file *uploaderFile
|
||||
thumb *uploaderFile
|
||||
to peers.Peer
|
||||
|
||||
asPhoto bool
|
||||
remove bool
|
||||
}
|
||||
|
||||
func (e *iterElem) File() uploader.File {
|
||||
return e.file
|
||||
}
|
||||
|
||||
func (e *iterElem) Thumb() (uploader.File, bool) {
|
||||
if e.thumb == nil {
|
||||
return nil, false
|
||||
}
|
||||
return e.thumb, true
|
||||
}
|
||||
|
||||
func (e *iterElem) To() tg.InputPeerClass {
|
||||
return e.to.InputPeer()
|
||||
}
|
||||
|
||||
func (e *iterElem) AsPhoto() bool {
|
||||
return e.asPhoto
|
||||
}
|
||||
|
||||
type uploaderFile struct {
|
||||
*os.File
|
||||
size int64
|
||||
}
|
||||
|
||||
func (u *uploaderFile) Name() string {
|
||||
return filepath.Base(u.File.Name())
|
||||
}
|
||||
|
||||
func (u *uploaderFile) Size() int64 {
|
||||
return u.size
|
||||
}
|
@ -3,7 +3,6 @@ package up
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/gabriel-vasile/mimetype"
|
||||
"github.com/go-faster/errors"
|
||||
@ -26,18 +25,19 @@ type iter struct {
|
||||
|
||||
cur int
|
||||
err error
|
||||
file *uploader.Elem
|
||||
file uploader.Elem
|
||||
}
|
||||
|
||||
func newIter(files []*file, to peers.Peer, photo, remove bool) *iter {
|
||||
return &iter{
|
||||
files: files,
|
||||
cur: 0,
|
||||
err: nil,
|
||||
file: nil,
|
||||
to: to,
|
||||
photo: photo,
|
||||
remove: remove,
|
||||
|
||||
cur: 0,
|
||||
err: nil,
|
||||
file: nil,
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,25 +56,13 @@ func (i *iter) Next(ctx context.Context) bool {
|
||||
cur := i.files[i.cur]
|
||||
i.cur++
|
||||
|
||||
fMime, err := mimetype.DetectFile(cur.file)
|
||||
if err != nil {
|
||||
i.err = errors.Wrap(err, "detect mime")
|
||||
return false
|
||||
}
|
||||
|
||||
f, err := os.Open(cur.file)
|
||||
if err != nil {
|
||||
i.err = errors.Wrap(err, "open file")
|
||||
return false
|
||||
}
|
||||
|
||||
stat, err := f.Stat()
|
||||
if err != nil {
|
||||
i.err = errors.Wrap(err, "stat file")
|
||||
return false
|
||||
}
|
||||
|
||||
var thumb uploader.File = nopFile{}
|
||||
var thumb *uploaderFile = nil
|
||||
// has thumbnail
|
||||
if cur.thumb != "" {
|
||||
tMime, err := mimetype.DetectFile(cur.thumb)
|
||||
@ -88,53 +76,31 @@ func (i *iter) Next(ctx context.Context) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
thumb = uploaderFile{thumbFile}
|
||||
thumb = &uploaderFile{File: thumbFile, size: 0}
|
||||
}
|
||||
|
||||
i.file = &uploader.Elem{
|
||||
File: uploaderFile{f},
|
||||
Thumb: thumb,
|
||||
Name: filepath.Base(f.Name()),
|
||||
MIME: fMime.String(),
|
||||
Size: stat.Size(),
|
||||
To: i.to,
|
||||
Photo: i.photo,
|
||||
Remove: i.remove,
|
||||
stat, err := f.Stat()
|
||||
if err != nil {
|
||||
i.err = errors.Wrap(err, "stat file")
|
||||
return false
|
||||
}
|
||||
|
||||
i.file = &iterElem{
|
||||
file: &uploaderFile{File: f, size: stat.Size()},
|
||||
thumb: thumb,
|
||||
to: i.to,
|
||||
|
||||
asPhoto: i.photo,
|
||||
remove: i.remove,
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (i *iter) Value() *uploader.Elem {
|
||||
func (i *iter) Value() uploader.Elem {
|
||||
return i.file
|
||||
}
|
||||
|
||||
func (i *iter) Err() error {
|
||||
return i.err
|
||||
}
|
||||
|
||||
type nopFile struct{}
|
||||
|
||||
func (nopFile) Read(_ []byte) (n int, err error) {
|
||||
return 0, errors.New("nopFile")
|
||||
}
|
||||
|
||||
func (nopFile) Seek(_ int64, _ int) (int64, error) {
|
||||
return 0, errors.New("nopFile")
|
||||
}
|
||||
|
||||
func (nopFile) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nopFile) Remove() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type uploaderFile struct {
|
||||
*os.File
|
||||
}
|
||||
|
||||
func (u uploaderFile) Remove() error {
|
||||
return os.Remove(u.Name())
|
||||
}
|
||||
|
@ -2,9 +2,11 @@ package up
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/go-faster/errors"
|
||||
pw "github.com/jedib0t/go-pretty/v6/progress"
|
||||
|
||||
"github.com/iyear/tdl/pkg/prog"
|
||||
@ -29,12 +31,12 @@ func newProgress(p pw.Writer) *progress {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *progress) OnAdd(elem *uploader.Elem) {
|
||||
tracker := prog.AppendTracker(p.pw, utils.Byte.FormatBinaryBytes, p.processMessage(elem), elem.Size)
|
||||
func (p *progress) OnAdd(elem uploader.Elem) {
|
||||
tracker := prog.AppendTracker(p.pw, utils.Byte.FormatBinaryBytes, p.processMessage(elem), elem.File().Size())
|
||||
p.trackers.Store(p.tuple(elem), tracker)
|
||||
}
|
||||
|
||||
func (p *progress) OnUpload(elem *uploader.Elem, state uploader.ProgressState) {
|
||||
func (p *progress) OnUpload(elem uploader.Elem, state uploader.ProgressState) {
|
||||
tracker, ok := p.trackers.Load(p.tuple(elem))
|
||||
if !ok {
|
||||
return
|
||||
@ -45,28 +47,60 @@ func (p *progress) OnUpload(elem *uploader.Elem, state uploader.ProgressState) {
|
||||
t.SetValue(state.Uploaded)
|
||||
}
|
||||
|
||||
func (p *progress) OnDone(elem *uploader.Elem, err error) {
|
||||
func (p *progress) OnDone(elem uploader.Elem, err error) {
|
||||
tracker, ok := p.trackers.Load(p.tuple(elem))
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
t := tracker.(*pw.Tracker)
|
||||
if err != nil {
|
||||
p.pw.Log(color.RedString("%s error: %s", p.elemString(elem), err.Error()))
|
||||
t.MarkAsErrored()
|
||||
e := elem.(*iterElem)
|
||||
|
||||
if err := p.closeFile(e); err != nil {
|
||||
p.fail(t, elem, errors.Wrap(err, "close file"))
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
p.fail(t, elem, errors.Wrap(err, "progress"))
|
||||
return
|
||||
}
|
||||
|
||||
if e.remove {
|
||||
if err := os.Remove(e.file.File.Name()); err != nil {
|
||||
p.fail(t, elem, errors.Wrap(err, "remove file"))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *progress) tuple(elem *uploader.Elem) tuple {
|
||||
return tuple{elem.Name, elem.To.ID()}
|
||||
func (p *progress) closeFile(e *iterElem) error {
|
||||
if err := e.file.Close(); err != nil {
|
||||
return errors.Wrap(err, "close file")
|
||||
}
|
||||
|
||||
if e.thumb != nil {
|
||||
if err := e.thumb.Close(); err != nil {
|
||||
return errors.Wrap(err, "close thumb")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *progress) processMessage(elem *uploader.Elem) string {
|
||||
func (p *progress) fail(t *pw.Tracker, elem uploader.Elem, err error) {
|
||||
p.pw.Log(color.RedString("%s error: %s", p.elemString(elem), err.Error()))
|
||||
t.MarkAsErrored()
|
||||
}
|
||||
|
||||
func (p *progress) tuple(elem uploader.Elem) tuple {
|
||||
return tuple{elem.(*iterElem).file.File.Name(), elem.(*iterElem).to.ID()}
|
||||
}
|
||||
|
||||
func (p *progress) processMessage(elem uploader.Elem) string {
|
||||
return p.elemString(elem)
|
||||
}
|
||||
|
||||
func (p *progress) elemString(elem *uploader.Elem) string {
|
||||
return fmt.Sprintf("%s -> %s(%d)", elem.Name, elem.To.VisibleName(), elem.To.ID())
|
||||
func (p *progress) elemString(elem uploader.Elem) string {
|
||||
e := elem.(*iterElem)
|
||||
return fmt.Sprintf("%s -> %s(%d)", e.file.File.Name(), e.to.VisibleName(), e.to.ID())
|
||||
}
|
||||
|
@ -57,6 +57,7 @@ func Run(ctx context.Context, opts *Options) error {
|
||||
}
|
||||
|
||||
upProgress := prog.New(utils.Byte.FormatBinaryBytes)
|
||||
upProgress.SetNumTrackersExpected(len(files))
|
||||
prog.EnablePS(ctx, upProgress)
|
||||
|
||||
options := uploader.Options{
|
||||
|
@ -4,27 +4,24 @@ import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/gotd/td/telegram/peers"
|
||||
"github.com/gotd/td/tg"
|
||||
)
|
||||
|
||||
type Iter interface {
|
||||
Next(ctx context.Context) bool
|
||||
Value() *Elem
|
||||
Value() Elem
|
||||
Err() error
|
||||
}
|
||||
|
||||
type File interface {
|
||||
io.ReadSeekCloser
|
||||
Remove() error
|
||||
io.ReadSeeker
|
||||
Name() string
|
||||
Size() int64
|
||||
}
|
||||
|
||||
type Elem struct {
|
||||
File File
|
||||
Thumb File
|
||||
Name string
|
||||
MIME string
|
||||
Size int64
|
||||
To peers.Peer
|
||||
Photo bool
|
||||
Remove bool
|
||||
type Elem interface {
|
||||
File() File
|
||||
Thumb() (File, bool)
|
||||
To() tg.InputPeerClass
|
||||
AsPhoto() bool
|
||||
}
|
||||
|
@ -7,9 +7,9 @@ import (
|
||||
)
|
||||
|
||||
type Progress interface {
|
||||
OnAdd(elem *Elem)
|
||||
OnUpload(elem *Elem, state ProgressState)
|
||||
OnDone(elem *Elem, err error)
|
||||
OnAdd(elem Elem)
|
||||
OnUpload(elem Elem, state ProgressState)
|
||||
OnDone(elem Elem, err error)
|
||||
// TODO: OnLog to log something that is not an error but should be sent to the user
|
||||
}
|
||||
|
||||
@ -19,7 +19,7 @@ type ProgressState struct {
|
||||
}
|
||||
|
||||
type wrapProcess struct {
|
||||
elem *Elem
|
||||
elem Elem
|
||||
process Progress
|
||||
}
|
||||
|
||||
|
@ -2,16 +2,15 @@ package uploader
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/gabriel-vasile/mimetype"
|
||||
"github.com/go-faster/errors"
|
||||
"github.com/gotd/td/telegram/message"
|
||||
"github.com/gotd/td/telegram/message/styling"
|
||||
"github.com/gotd/td/telegram/uploader"
|
||||
"github.com/gotd/td/tg"
|
||||
"go.uber.org/multierr"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/iyear/tdl/pkg/utils"
|
||||
@ -64,17 +63,7 @@ func (u *Uploader) Upload(ctx context.Context, limit int) error {
|
||||
return wg.Wait()
|
||||
}
|
||||
|
||||
func (u *Uploader) upload(ctx context.Context, elem *Elem) (rerr error) {
|
||||
defer func() {
|
||||
if rerr == nil && elem.Remove {
|
||||
multierr.AppendInto(&rerr, elem.File.Remove())
|
||||
multierr.AppendInto(&rerr, elem.Thumb.Remove())
|
||||
}
|
||||
}()
|
||||
|
||||
defer multierr.AppendInvoke(&rerr, multierr.Close(elem.File))
|
||||
defer multierr.AppendInvoke(&rerr, multierr.Close(elem.Thumb))
|
||||
|
||||
func (u *Uploader) upload(ctx context.Context, elem Elem) error {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
@ -89,48 +78,64 @@ func (u *Uploader) upload(ctx context.Context, elem *Elem) (rerr error) {
|
||||
process: u.opts.Progress,
|
||||
})
|
||||
|
||||
f, err := up.Upload(ctx, uploader.NewUpload(elem.Name, elem.File, elem.Size))
|
||||
f, err := up.Upload(ctx, uploader.NewUpload(elem.File().Name(), elem.File(), elem.File().Size()))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "upload file")
|
||||
}
|
||||
|
||||
caption := []message.StyledTextOption{
|
||||
styling.Code(elem.Name),
|
||||
styling.Plain(" - "),
|
||||
styling.Code(elem.MIME),
|
||||
if _, err = elem.File().Seek(0, io.SeekStart); err != nil {
|
||||
return errors.Wrap(err, "seek file")
|
||||
}
|
||||
doc := message.UploadedDocument(f, caption...).MIME(elem.MIME).Filename(elem.Name)
|
||||
mime, err := mimetype.DetectReader(elem.File())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "detect mime")
|
||||
}
|
||||
|
||||
caption := []message.StyledTextOption{
|
||||
styling.Code(elem.File().Name()),
|
||||
styling.Plain(" - "),
|
||||
styling.Code(mime.String()),
|
||||
}
|
||||
doc := message.UploadedDocument(f, caption...).
|
||||
MIME(mime.String()).
|
||||
Filename(elem.File().Name())
|
||||
// upload thumbnail TODO(iyear): maybe still unavailable
|
||||
if thumb, err := uploader.NewUploader(u.opts.Client).
|
||||
FromReader(ctx, fmt.Sprintf("%s.thumb", elem.Name), elem.Thumb); err == nil {
|
||||
doc = doc.Thumb(thumb)
|
||||
if thumb, ok := elem.Thumb(); ok {
|
||||
if thumbFile, err := uploader.NewUploader(u.opts.Client).
|
||||
FromReader(ctx, thumb.Name(), thumb); err == nil {
|
||||
doc = doc.Thumb(thumbFile)
|
||||
}
|
||||
}
|
||||
|
||||
var media message.MediaOption = doc
|
||||
|
||||
switch {
|
||||
case utils.Media.IsImage(elem.MIME) && elem.Photo:
|
||||
case utils.Media.IsImage(mime.String()) && elem.AsPhoto():
|
||||
// webp should be uploaded as document
|
||||
if mime.String() == "image/webp" {
|
||||
break
|
||||
}
|
||||
// upload as photo
|
||||
media = message.UploadedPhoto(f, caption...)
|
||||
case utils.Media.IsVideo(elem.MIME):
|
||||
case utils.Media.IsVideo(mime.String()):
|
||||
// reset reader
|
||||
if _, err = elem.File.Seek(0, io.SeekStart); err != nil {
|
||||
if _, err = elem.File().Seek(0, io.SeekStart); err != nil {
|
||||
return errors.Wrap(err, "seek file")
|
||||
}
|
||||
if dur, w, h, err := utils.Media.GetMP4Info(elem.File); err == nil {
|
||||
if dur, w, h, err := utils.Media.GetMP4Info(elem.File()); err == nil {
|
||||
// #132. There may be some errors, but we can still upload the file
|
||||
media = doc.Video().
|
||||
Duration(time.Duration(dur)*time.Second).
|
||||
Resolution(w, h).
|
||||
SupportsStreaming()
|
||||
}
|
||||
case utils.Media.IsAudio(elem.MIME):
|
||||
media = doc.Audio().Title(utils.FS.GetNameWithoutExt(elem.Name))
|
||||
case utils.Media.IsAudio(mime.String()):
|
||||
media = doc.Audio().Title(utils.FS.GetNameWithoutExt(elem.File().Name()))
|
||||
}
|
||||
|
||||
_, err = message.NewSender(u.opts.Client).
|
||||
WithUploader(up).
|
||||
To(elem.To.InputPeer()).
|
||||
To(elem.To()).
|
||||
Media(ctx, media)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "send message")
|
||||
|
Loading…
Reference in New Issue
Block a user