/// <summary> /// Retrieves a hash for the given file, either cached from this file content table or by hashing the file. /// If the file must be hashed (cache miss), the computed hash is stored in the file content table. /// </summary> /// <exception cref="BuildXLException">Thrown if accessing the local path specified by 'key' fails.</exception> public static async Task <VersionedFileIdentityAndContentInfoWithOrigin> GetAndRecordContentHashAsync( this FileContentTable fileContentTable, FileStream contentStream, bool?strict = default, bool ignoreKnownContentHash = false) { Contract.Requires(fileContentTable != null); Contract.Requires(contentStream != null); if (!ignoreKnownContentHash) { VersionedFileIdentityAndContentInfo?existingInfo = fileContentTable.TryGetKnownContentHash(contentStream); if (existingInfo.HasValue) { return(new VersionedFileIdentityAndContentInfoWithOrigin(existingInfo.Value, ContentHashOrigin.Cached)); } } ContentHash newHash = await ContentHashingUtilities.HashContentStreamAsync(contentStream); VersionedFileIdentityAndContentInfo newInfo = fileContentTable.RecordContentHash( contentStream, newHash, strict: strict); return(new VersionedFileIdentityAndContentInfoWithOrigin(newInfo, ContentHashOrigin.NewlyHashed)); }
/// <summary> /// Performs a smart write in which no write is performed if the destination already has the same content as the source /// (as provided in <paramref name="contentsHash" />). /// Note that the destination may be replaced if it exists (otherwise there's no use in comparing hashes). /// </summary> /// <remarks> /// Note that <paramref name="contentsHash" /> must be faithful to <paramref name="contents" />, since that hash is /// recorded for <paramref name="destinationPath" /> /// if a copy is performed. /// </remarks> /// <returns>A bool indicating if the content was mismatched and thus a full write was performed.</returns> public static async Task <ConditionalUpdateResult> WriteBytesIfContentMismatchedAsync( this FileContentTable fileContentTable, string destinationPath, byte[] contents, ContentHash contentsHash) { Contract.Requires(fileContentTable != null); Contract.Requires(!string.IsNullOrEmpty(destinationPath)); Contract.Requires(contents != null); VersionedFileIdentityAndContentInfo?destinationInfo = null; bool written = await FileUtilities.WriteAllBytesAsync( destinationPath, contents, predicate : handle => { // Nonexistent destination? if (handle == null) { return(true); } VersionedFileIdentityAndContentInfo?known = fileContentTable.TryGetKnownContentHash(destinationPath, handle); // We return true (proceed) if there's a hash mismatch. if (!known.HasValue || known.Value.FileContentInfo.Hash != contentsHash) { return(true); } destinationInfo = known.Value; return(false); }, onCompletion : handle => { Contract.Assume(destinationInfo == null); VersionedFileIdentity identity = fileContentTable.RecordContentHash( destinationPath, handle, contentsHash, contents.Length, strict: true); destinationInfo = new VersionedFileIdentityAndContentInfo( identity, new FileContentInfo(contentsHash, contents.Length)); }); Contract.Assume(destinationInfo != null); return(new ConditionalUpdateResult(!written, destinationInfo.Value)); }
/// <summary> /// Performs a smart copy in which no writes are performed if the destination already has the same content as the source /// (as provided in <paramref name="sourceContentInfo" />). /// Note that the destination may be replaced if it exists (otherwise there's no use in comparing hashes). /// </summary> /// <remarks> /// Note that <paramref name="sourceContentInfo" /> should be faithful to <paramref name="sourcePath" />, since that hash is /// to be recorded for <paramref name="destinationPath" />. /// </remarks> /// <returns>Indicates if the copy was elided (up-to-date) or actually performed.</returns> public static async Task <ConditionalUpdateResult> CopyIfContentMismatchedAsync( this FileContentTable fileContentTable, string sourcePath, string destinationPath, FileContentInfo sourceContentInfo) { Contract.Requires(!string.IsNullOrEmpty(sourcePath)); Contract.Requires(!string.IsNullOrEmpty(destinationPath)); VersionedFileIdentityAndContentInfo?destinationInfo = null; bool copied = await FileUtilities.CopyFileAsync( sourcePath, destinationPath, predicate : (source, dest) => { // Nonexistent destination? if (dest == null) { return(true); } VersionedFileIdentityAndContentInfo?knownDestinationInfo = fileContentTable.TryGetKnownContentHash(destinationPath, dest); if (!knownDestinationInfo.HasValue || knownDestinationInfo.Value.FileContentInfo.Hash != sourceContentInfo.Hash) { return(true); } destinationInfo = knownDestinationInfo.Value; return(false); }, onCompletion : (source, dest) => { Contract.Assume( destinationInfo == null, "onCompletion should only happen when we committed to a copy (and then, we shouldn't have a destination version yet)."); VersionedFileIdentity identity = fileContentTable.RecordContentHash( destinationPath, dest, sourceContentInfo.Hash, sourceContentInfo.Length, strict: true); destinationInfo = new VersionedFileIdentityAndContentInfo(identity, sourceContentInfo); }); Contract.Assume(destinationInfo != null); return(new ConditionalUpdateResult(!copied, destinationInfo.Value)); }
GetAndRecordContentHashAsync( this FileContentTable fileContentTable, string path, bool?strict = default, Action <SafeFileHandle, VersionedFileIdentityAndContentInfoWithOrigin> beforeClose = null, bool ignoreKnownContentHash = false) { Contract.Requires(fileContentTable != null); Contract.Requires(path != null); if (beforeClose == null && !ignoreKnownContentHash) { // Due to path mapping in FileContentTable, querying with path will be much faster than opening a handle for a stream. VersionedFileIdentityAndContentInfo?existingInfo = fileContentTable.TryGetKnownContentHash(path); if (existingInfo.HasValue) { return(new VersionedFileIdentityAndContentInfoWithOrigin(existingInfo.Value, ContentHashOrigin.Cached)); } } using ( FileStream contentStream = FileUtilities.CreateFileStream( path, FileMode.Open, strict == true ? FileAccess.ReadWrite : FileAccess.Read, FileShare.Read | FileShare.Delete, FileOptions.None, force: strict == true)) { VersionedFileIdentityAndContentInfoWithOrigin newInfo = await fileContentTable.GetAndRecordContentHashAsync( contentStream, strict : strict, ignoreKnownContentHash : beforeClose == null || ignoreKnownContentHash); beforeClose?.Invoke(contentStream.SafeFileHandle, newInfo); return(newInfo); } }