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] }