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 }