Files
mautrix-telegram/pkg/gotd/tgtest/services/file/upload.go
T
2025-06-27 20:03:37 -07:00

116 lines
2.8 KiB
Go

package file
import (
"context"
"fmt"
"github.com/go-faster/errors"
"go.uber.org/multierr"
"go.mau.fi/mautrix-telegram/pkg/gotd/constant"
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
"go.mau.fi/mautrix-telegram/pkg/gotd/tgerr"
)
// https://core.telegram.org/api/files#uploading-files
const (
// Each part should have a sequence number, file_part, with a value ranging from 0 to 3,999.
uploadPartsLimit = constant.UploadMaxParts
// `part_size % 1024 = 0` (divisible by 1KB)
uploadPaddingPartSize = constant.UploadPadding
// `524288 % part_size = 0` (512KB must be evenly divisible by part_size)
uploadMaximumPartSize = constant.UploadMaxPartSize
)
type upload interface {
// GetFileID returns random file identifier created by the client.
GetFileID() int64
// GetFilePart returns numerical order of a part.
GetFilePart() int
// GetBytes returns binary data, content of a part.
GetBytes() []byte
}
func validatePartSize(got, stored int) *tgerr.Error {
switch {
case got == 0:
return tgerr.New(400, tg.ErrFilePartEmpty)
case got > uploadMaximumPartSize:
return tgerr.New(400, tg.ErrFilePartTooBig)
}
if stored == 0 {
return nil
}
switch {
case got != stored:
return tgerr.New(400, tg.ErrFilePartSizeChanged)
case uploadMaximumPartSize%got != 0,
got%uploadPaddingPartSize != 0:
return tgerr.New(400, tg.ErrFilePartSizeInvalid)
default:
return nil
}
}
func (m *Service) write(ctx context.Context, request upload) (err error) {
// TODO(tdakkota): Better way to handle user id. For now we haven't auth service to pair
// user ID and authkey
id, ok := ctx.Value("user_id").(int)
if !ok {
id = 10
}
file, err := m.storage.Open(fmt.Sprintf("%d_%d", id, request.GetFileID()))
if err != nil {
return errors.Wrap(err, "open file")
}
defer func() {
multierr.AppendInto(&err, file.Close())
}()
part := request.GetFilePart()
if part < 0 || part > uploadPartsLimit {
return tgerr.New(400, tg.ErrFilePartInvalid)
}
data := request.GetBytes()
partSize := file.PartSize()
if err := validatePartSize(len(data), partSize); err != nil {
return err
}
if partSize == 0 {
partSize = len(data)
file.SetPartSize(partSize)
}
offset := int64(partSize * part)
if _, err := file.WriteAt(data, offset); err != nil {
return errors.Errorf("write at %d-%d", offset, offset+int64(len(data)))
}
return nil
}
func (m *Service) UploadSaveFilePart(ctx context.Context, request *tg.UploadSaveFilePartRequest) (bool, error) {
if err := m.write(ctx, request); err != nil {
return false, err
}
return true, nil
}
func (m *Service) UploadSaveBigFilePart(ctx context.Context, request *tg.UploadSaveBigFilePartRequest) (bool, error) {
part := request.FileTotalParts
if part < 0 || part > uploadPartsLimit {
return false, tgerr.New(400, tg.ErrFilePartsInvalid)
}
if err := m.write(ctx, request); err != nil {
return false, err
}
return true, nil
}