Repository: rclone/rclone
File: backend/zoho/zoho.go (Go)

Code before:

	return &info, nil
}

// Put the object into the container
//
// Copy the reader in to the new object which is returned.
//
// The new object may have been created if an error is returned
func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
	existingObj, err := f.NewObject(ctx, src.Remote())
	switch err {
	case nil:
		return existingObj, existingObj.Update(ctx, in, src, options...)
	case fs.ErrorObjectNotFound:
		size := src.Size()
		remote := src.Remote()

		// Create the directory for the object if it doesn't exist
		leaf, directoryID, err := f.dirCache.FindPath(ctx, remote, true)
		if err != nil {
			return nil, err
		}

		// use normal upload API for small sizes (<10MiB)
		if size < 10*1024*1024 {
			info, err := f.upload(ctx, f.opt.Enc.FromStandardName(leaf), directoryID, size, in, options...)
			if err != nil {
				return nil, err
			}

			return f.newObjectWithInfo(ctx, remote, info)
		}

		// large file API otherwise
		info, err := f.uploadLargeFile(ctx, f.opt.Enc.FromStandardName(leaf), directoryID, size, in, options...)
		if err != nil {
			return nil, err
		}

		return f.newObjectWithInfo(ctx, remote, info)
	default:
		return nil, err
	}
}

// Mkdir creates the container if it doesn't exist
func (f *Fs) Mkdir(ctx context.Context, dir string) error {
	_, err := f.dirCache.FindDir(ctx, dir, true)
	return err
}

Review comment (suggestion):
On other backends which do this we allow this to be configured with an upload cutoff

Eg s3

https://github.com/rclone/rclone/blob/2d1c2b1f76f44b4db5369bacdd4e5f76feecc128/backend/s3/s3.go#L2119-L2126

```
      --azureblob-upload-cutoff string       Cutoff for switching to chunked upload (<= 256 MiB) (deprecated)
      --b2-upload-cutoff SizeSuffix          Cutoff for switching to chunked upload (default 200Mi)
      --box-upload-cutoff SizeSuffix         Cutoff for switching to multipart upload (>= 50 MiB) (default 50Mi)
      --drive-upload-cutoff SizeSuffix       Cutoff for switching to chunked upload (default 8Mi)
      --hidrive-upload-cutoff SizeSuffix     Cutoff/Threshold for chunked uploads (default 96Mi)
      --oos-upload-cutoff SizeSuffix         Cutoff for switching to chunked upload (default 200Mi)
      --qingstor-upload-cutoff SizeSuffix    Cutoff for switching to chunked upload (default 200Mi)
      --s3-upload-cutoff SizeSuffix          Cutoff for switching to chunked upload (default 200Mi)
      --sharefile-upload-cutoff SizeSuffix   Cutoff for switching to multipart upload (default 128Mi)
```

It would probably be a good idea to add that here just in case users need to tweak it

Code after:
	}
	return
}

func (f *Fs) uploadLargeFile(ctx context.Context, name string, parent string, size int64, in io.Reader, options ...fs.OpenOption) (*api.Item, error) {
	opts := rest.Opts{
		Method:        "POST",
		Path:          "/stream/upload",
		Body:          in,
		ContentLength: &size,
		ContentType:   "application/octet-stream",
		Options:       options,
		ExtraHeaders: map[string]string{
			"x-filename":          url.QueryEscape(name),
			"x-parent_id":         parent,
			"override-name-exist": "true",
			"upload-id":           uuid.New().String(),
			"x-streammode":        "1",
		},
	}

	var err error
	var resp *http.Response
	var uploadResponse *api.LargeUploadResponse
	err = f.pacer.CallNoRetry(func() (bool, error) {
		resp, err = f.uploadsrv.CallJSON(ctx, &opts, nil, &uploadResponse)
		return shouldRetry(ctx, resp, err)
	})
	if err != nil {
		return nil, fmt.Errorf("upload large error: %v", err)
	}
	if len(uploadResponse.Uploads) != 1 {
		return nil, errors.New("upload: invalid response")
	}
	upload := uploadResponse.Uploads[0]
	uploadInfo, err := upload.GetUploadFileInfo()
	if err != nil {
		return nil, fmt.Errorf("upload error: %w", err)
	}

	// Fill in the api.Item from the api.UploadFileInfo
	var info api.Item
	info.ID = upload.Attributes.RessourceID
	info.Attributes.Name = upload.Attributes.FileName
	// info.Attributes.Type = not used
	info.Attributes.IsFolder = false
	// info.Attributes.CreatedTime = not used
	info.Attributes.ModifiedTime = uploadInfo.GetModTime()
	// info.Attributes.UploadedTime = 0 not used
	info.Attributes.StorageInfo.Size = uploadInfo.Size
	info.Attributes.StorageInfo.FileCount = 0