using static MFiles.Grpc.Samples.MFilesValues;
namespace MFiles.Grpc.Samples.Recipes;
///
/// Recipe "download-file": download the files of a document's latest version to a folder.
/// Arguments: [document ID, default 437] [folder, default a "mfiles-grpc-samples" folder in TEMP]
///
public static class DownloadFile
{
/// Bytes per DownloadFile_ReadBlock call; the server allows at most 4 MiB.
private const int BlockSize = 4 * 1024 * 1024;
public static async Task RunAsync(MFilesSettings settings, string[] args)
{
int id = RecipeHelpers.IntArg(args, 0, 437);
string folder = RecipeHelpers.StringArg(args, 1) ?? Path.Combine(Path.GetTempPath(), "mfiles-grpc-samples");
Directory.CreateDirectory(folder);
await using var connection = await MFilesConnection.ConnectAsync(settings, RecipeHelpers.ClientName);
var objects = new IRPCObjectOperations.IRPCObjectOperationsClient(connection.CallInvoker);
// The latest version lists the files.
var latest = (await objects.GetObjectInfoAndPropertiesAsync(new GetObjectInfoAndPropertiesRequest
{
ObjId = ObjId(0, id),
LatestVersion = true,
})).ObjectInfo;
Console.WriteLine(RecipeHelpers.Describe(latest));
foreach (var file in latest.VersionInfo.Files)
{
string name = $"{file.Title}.{file.Extension}";
// A title can contain characters that are not allowed in file names, such as "/".
string path = Path.Combine(folder, SafeFileName(name));
long size = await DownloadAsync(objects, latest.ObjectInfo.ObjId, file.FileVer, path);
Console.WriteLine($" Downloaded {name} ({size} bytes) to {path}");
}
}
/// Replaces the characters Windows does not allow in file names.
private static string SafeFileName(string name) =>
string.Concat(name.Select(c => "<>:\"/\\|?*".Contains(c) ? '_' : c));
// [START download-file]
///
/// Downloads one file version in blocks: DownloadFile_Begin opens a download session,
/// DownloadFile_ReadBlock reads it, DownloadFile_Stop closes it.
///
private static async Task 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 });
}
}
// [END download-file]
}