# Download a file

Location: [gRPC API](https://developer.m-files.com/APIs/gRPC-API/index.md) > [Guides](https://developer.m-files.com/APIs/gRPC-API/index.md#guides)

HTML version: https://developer.m-files.com/APIs/gRPC-API/guides/download-file/

The files of an object version are listed in its version information. To get the content, open a
download session for one file version, read it in blocks and close the session.

## Calls

1. [GetObjectInfoAndProperties](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/GetObjectInfoAndProperties/index.md)
   (or a search result): `object_info.version_info.files` lists the
   [File](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.File/index.md)s with `title`, `extension`, `size` and `file_ver`.
2. [DownloadFile_Begin](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/DownloadFile_Begin/index.md) with
   `obj_file_ver` (the object's `obj_id` and the file's `file_ver`), `format`
   `FILE_FORMAT_NATIVE` and `prepare_file_for_downloading` true. It returns `download_id` and
   `file_outline.size`.
3. [DownloadFile_ReadBlock](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/DownloadFile_ReadBlock/index.md) with
   `download_id`, `offset` and `block_size` until you have `size` bytes. The samples ask for
   4 MiB, or for the rest of the file if that is less.
4. [DownloadFile_Stop](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/DownloadFile_Stop/index.md) with `download_id`,
   also after an error.

The recipe downloads every file of the latest version of a document (default ID 437) into a
folder (default `mfiles-grpc-samples` in the temporary folder).

## Code

The function that downloads one file version:

### Python

```python
def download(objects, object_id: structs.ObjID, file_ver: structs.FileVer, path: Path) -> int:
    """Downloads one file version in blocks: DownloadFile_Begin opens a download session,
    DownloadFile_ReadBlock reads it, DownloadFile_Stop closes it."""
    begin = objects.DownloadFile_Begin(ops.DownloadFile_BeginRequest(
        obj_file_ver=structs.ObjFileVer(obj_id=object_id, file_ver=file_ver),
        format=enums.FILE_FORMAT_NATIVE,
        prepare_file_for_downloading=True))
    try:
        size = begin.file_outline.size
        with open(path, "wb") as output:
            offset = 0
            while offset < size:
                block = objects.DownloadFile_ReadBlock(ops.DownloadFile_ReadBlockRequest(
                    download_id=begin.download_id,
                    block_size=min(BLOCK_SIZE, size - offset),
                    offset=offset))
                if not block.data:
                    raise IOError(f"The server returned no data at offset {offset} of {size}.")
                output.write(block.data)
                offset += len(block.data)
        return size
    finally:
        objects.DownloadFile_Stop(ops.DownloadFile_StopRequest(download_id=begin.download_id))
```

*Source: [python/recipes/download_file.py](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/python/recipes/download_file.py)*

### C#

```csharp
/// <summary>
/// Downloads one file version in blocks: DownloadFile_Begin opens a download session,
/// DownloadFile_ReadBlock reads it, DownloadFile_Stop closes it.
/// </summary>
private static async Task<long> DownloadAsync(IRPCObjectOperations.IRPCObjectOperationsClient objects,
    ObjID objId, FileVer fileVer, string path)
{
    var begin = await objects.DownloadFile_BeginAsync(new DownloadFile_BeginRequest
    {
        ObjFileVer = new ObjFileVer { ObjId = objId, FileVer = fileVer },
        Format = FileFormat.Native,
        PrepareFileForDownloading = true,
    });
    try
    {
        await using var output = System.IO.File.Create(path);
        long size = begin.FileOutline.Size;
        for (long offset = 0; offset < size;)
        {
            var block = await objects.DownloadFile_ReadBlockAsync(new DownloadFile_ReadBlockRequest
            {
                DownloadId = begin.DownloadId,
                BlockSize = (uint)Math.Min(BlockSize, size - offset),
                Offset = offset,
            });
            if (block.Data.IsEmpty)
                throw new IOException($"The server returned no data at offset {offset} of {size}.");
            block.Data.WriteTo(output);
            offset += block.Data.Length;
        }
        return size;
    }
    finally
    {
        await objects.DownloadFile_StopAsync(new DownloadFile_StopRequest { DownloadId = begin.DownloadId });
    }
}
```

*Source: [csharp/Recipes/DownloadFile.cs](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/csharp/Recipes/DownloadFile.cs)*

### Go

```go

// download saves one file version in blocks: DownloadFile_Begin opens a download session,
// DownloadFile_ReadBlock reads it, DownloadFile_Stop closes it.
func download(ctx context.Context, objects mfiles.IRPCObjectOperationsClient, objID *mfiles.ObjID,
	fileVer *mfiles.FileVer, path string) (int64, error) {
	begin, err := objects.DownloadFile_Begin(ctx, &mfiles.DownloadFile_BeginRequest{
		ObjFileVer:                &mfiles.ObjFileVer{ObjId: objID, FileVer: fileVer},
		Format:                    mfiles.FileFormat_FILE_FORMAT_NATIVE,
		PrepareFileForDownloading: true,
	})
	if err != nil {
		return 0, err
	}
	downloadID := begin.GetDownloadId()
	defer objects.DownloadFile_Stop(context.WithoutCancel(ctx),
		&mfiles.DownloadFile_StopRequest{DownloadId: downloadID})

	output, err := os.Create(path)
	if err != nil {
		return 0, err
	}
	defer output.Close()

	size := begin.GetFileOutline().GetSize()
	for offset := int64(0); offset < size; {
		block, err := objects.DownloadFile_ReadBlock(ctx, &mfiles.DownloadFile_ReadBlockRequest{
			DownloadId: downloadID,
			BlockSize:  uint32(min(blockSize, size-offset)),
			Offset:     offset,
		})
		if err != nil {
			return 0, err
		}
		if len(block.GetData()) == 0 {
			return 0, fmt.Errorf("the server returned no data at offset %d of %d", offset, size)
		}
		if _, err := output.Write(block.GetData()); err != nil {
			return 0, err
		}
		offset += int64(len(block.GetData()))
	}
	return size, output.Close()
}

```

*Source: [go/recipes/download_file/main.go](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/go/recipes/download_file/main.go)*

Output:

```text
Proposal 7732 - ESTT Corporation (IT) (ID 437, version 1, modified 2012-06-07 10:39:43 UTC)
  Downloaded Proposal 7732 - ESTT Corporation (IT).doc (57344 bytes) to C:\Users\me\AppData\Local\Temp\mfiles-grpc-samples\Proposal 7732 - ESTT Corporation (IT).doc
```

## Pitfalls

* **`prepare_file_for_downloading` must be true.** Without it, `DownloadFile_Begin` only checks
  access and returns `download_id` 0, and `DownloadFile_ReadBlock` then fails with
  `0x8004000B` "Not found. (The specified download session 0 does not exist.)".
* **At most 4 MiB per block.** A larger `block_size` fails with `E_INVALIDARG` (`0x80070057`,
  "Block size exceeded the limit of 4194304 bytes."). The samples read blocks of at most 4 MiB;
  their helpers accept responses of any size, so the 4 MiB default of gRPC libraries does not get in
  the way. See [Files](https://developer.m-files.com/APIs/gRPC-API/runtime/files/index.md).
* **The session can end with the last block.** After a read that reaches the end of the file
  with a block larger than the rest, the session may already be gone. `DownloadFile_Stop`
  succeeds on a closed session too, so always call it, also after errors.
* **The session lives on one server instance.** On M-Files Cloud, keep the `mfilesmsm` cookie so
  that every block goes to the instance that holds the session; the helpers do.
* **File names.** `title` can contain characters that are not allowed in file names, such as
  `/`. Replace them before you write the file, as the recipes do.
* A document can have no files, or several (when the property Single file, 22, is false).

## See also

* [Files](https://developer.m-files.com/APIs/gRPC-API/runtime/files/index.md): download and upload in detail, formats and limits.
* [Create a document](https://developer.m-files.com/APIs/gRPC-API/guides/create-document/index.md) for the upload direction.
