Download a file

View as Markdown

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 (or a search result): object_info.version_info.files lists the Files with title, extension, size and file_ver.
  2. DownloadFile_Begin 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 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 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

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

C#

/// <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

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

Output:

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

See also