Skip to content

Commit

Permalink
Merge branch 'stable'
Browse files Browse the repository at this point in the history
* stable:
  (GH-1047) Robust/efficient xml serialization
  (GH-1047) Add "replace" filesystem operation
  (GH-1047) Add additional hash stream options to Providers
  • Loading branch information
ferventcoder committed Mar 23, 2017
2 parents 19ecfac + 5134b46 commit eec3fd6
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 23 deletions.
6 changes: 6 additions & 0 deletions src/chocolatey/infrastructure/adapters/HashAlgorithm.cs
@@ -1,5 +1,6 @@
namespace chocolatey.infrastructure.adapters
{
using System.IO;
using cryptography;

public sealed class HashAlgorithm : IHashAlgorithm
Expand All @@ -15,5 +16,10 @@ public byte[] ComputeHash(byte[] buffer)
{
return _algorithm.ComputeHash(buffer);
}

public byte[] ComputeHash(Stream stream)
{
return _algorithm.ComputeHash(stream);
}
}
}
3 changes: 3 additions & 0 deletions src/chocolatey/infrastructure/adapters/IHashAlgorithm.cs
@@ -1,10 +1,13 @@
namespace chocolatey.infrastructure.adapters
{
using System.IO;
// ReSharper disable InconsistentNaming

public interface IHashAlgorithm
{
byte[] ComputeHash(byte[] buffer);

byte[] ComputeHash(Stream stream);
}

// ReSharper restore InconsistentNaming
Expand Down
16 changes: 15 additions & 1 deletion src/chocolatey/infrastructure/cryptography/CryptoHashProvider.cs
@@ -1,4 +1,4 @@
// Copyright © 2011 - Present RealDimensions Software, LLC
// Copyright © 2011 - Present RealDimensions Software, LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -107,6 +107,20 @@ public string hash_file(string filePath)
}
}

public string hash_byte_array(byte[] buffer)
{
var hash = _hashAlgorithm.ComputeHash(buffer);

return BitConverter.ToString(hash).Replace("-", string.Empty);
}

public string hash_stream(Stream inputStream)
{
var hash = _hashAlgorithm.ComputeHash(inputStream);

return BitConverter.ToString(hash).Replace("-", string.Empty);
}

private static bool file_is_locked(Exception exception)
{
var errorCode = 0;
Expand Down
18 changes: 17 additions & 1 deletion src/chocolatey/infrastructure/cryptography/IHashProvider.cs
@@ -1,4 +1,4 @@
// Copyright © 2011 - Present RealDimensions Software, LLC
// Copyright © 2011 - Present RealDimensions Software, LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -13,6 +13,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

using System.IO;

namespace chocolatey.infrastructure.cryptography
{
/// <summary>
Expand All @@ -32,5 +34,19 @@ public interface IHashProvider
/// <param name="filePath">The file path.</param>
/// <returns>A computed hash of the file, based on the contents.</returns>
string hash_file(string filePath);

/// <summary>
/// Returns a hash of the specified stream.
/// </summary>
/// <param name="inputStream">The stream.</param>
/// <returns>A computed hash of the stream, based on the contents.</returns>
string hash_stream(Stream inputStream);

/// <summary>
/// Returns a hash of the specified byte array.
/// </summary>
/// <param name="buffer">The byte array.</param>
/// <returns>A computed hash of the array, based on the contents.</returns>
string hash_byte_array(byte[] buffer);
}
}
19 changes: 19 additions & 0 deletions src/chocolatey/infrastructure/filesystem/DotNetFileSystem.cs
Expand Up @@ -390,6 +390,25 @@ public bool copy_file_unsafe(string sourceFilePath, string destinationFilePath,
return success != 0;
}


public void replace_file(string sourceFilePath, string destinationFilePath, string backupFilePath)
{
this.Log().Debug(ChocolateyLoggers.Verbose, () => "Attempting to replace \"{0}\"{1} with \"{2}\". Backup placed at \"{3}\".".format_with(destinationFilePath, Environment.NewLine, sourceFilePath, backupFilePath));

allow_retries(
() =>
{
try
{
File.Replace(sourceFilePath, destinationFilePath, backupFilePath);
}
catch (IOException)
{
Alphaleonis.Win32.Filesystem.File.Replace(sourceFilePath, destinationFilePath, backupFilePath);
}
});
}

// ReSharper disable InconsistentNaming

// http://msdn.microsoft.com/en-us/library/windows/desktop/aa363851.aspx
Expand Down
8 changes: 8 additions & 0 deletions src/chocolatey/infrastructure/filesystem/IFileSystem.cs
Expand Up @@ -204,6 +204,14 @@ public interface IFileSystem
/// <returns>true if copy was successful, otherwise false</returns>
bool copy_file_unsafe(string sourceFilePath, string destinationFilePath, bool overwriteExisting);

/// <summary>
/// Replace an existing file.
/// </summary>
/// <param name="sourceFilePath">Where is the file now?</param>
/// <param name="destinationFilePath">Where would you like it to go?</param>
/// <param name="backupFilePath">Where should the existing file be placed? Null if nowhere.</param>
void replace_file(string sourceFilePath, string destinationFilePath, string backupFilePath);

/// <summary>
/// Deletes the specified file.
/// </summary>
Expand Down
93 changes: 72 additions & 21 deletions src/chocolatey/infrastructure/services/XmlService.cs
Expand Up @@ -15,6 +15,7 @@

namespace chocolatey.infrastructure.services
{
using System;
using System.IO;
using System.Text;
using System.Xml;
Expand Down Expand Up @@ -43,14 +44,48 @@ public XmlType deserialize<XmlType>(string xmlFilePath)
() =>
{
var xmlSerializer = new XmlSerializer(typeof(XmlType));
var xmlReader = XmlReader.Create(new StringReader(_fileSystem.read_file(xmlFilePath)));
if (!xmlSerializer.CanDeserialize(xmlReader))
using (var fileStream = _fileSystem.open_file_readonly(xmlFilePath))
using (var fileReader = new StreamReader(fileStream))
using (var xmlReader = XmlReader.Create(fileReader))
{
this.Log().Warn("Cannot deserialize response of type {0}", typeof(XmlType));
return default(XmlType);
}
if (!xmlSerializer.CanDeserialize(xmlReader))
{
this.Log().Warn("Cannot deserialize response of type {0}", typeof(XmlType));
return default(XmlType);
}
try
{
return (XmlType)xmlSerializer.Deserialize(xmlReader);
}
catch(InvalidOperationException ex)
{
// Check if its just a malformed document.
if (ex.Message.Contains("There is an error in XML document"))
{
// If so, check for a backup file and try an parse that.
if (_fileSystem.file_exists(xmlFilePath + ".backup"))
{
using (var backupStream = _fileSystem.open_file_readonly(xmlFilePath + ".backup"))
using (var backupReader = new StreamReader(backupStream))
using (var backupXmlReader = XmlReader.Create(backupReader))
{
var validConfig = (XmlType)xmlSerializer.Deserialize(backupXmlReader);
return (XmlType)xmlSerializer.Deserialize(xmlReader);
// If there's no errors and it's valid, go ahead and replace the bad file with the backup.
if(validConfig != null)
{
_fileSystem.copy_file(xmlFilePath + ".backup", xmlFilePath, overwriteExisting: true);
}
return validConfig;
}
}
}
throw;
}
}
},
"Error deserializing response of type {0}".format_with(typeof(XmlType)),
throwError: true);
Expand All @@ -65,30 +100,46 @@ public void serialize<XmlType>(XmlType xmlType, string xmlFilePath, bool isSilen
{
_fileSystem.create_directory_if_not_exists(_fileSystem.get_directory_name(xmlFilePath));

var xmlUpdateFilePath = xmlFilePath + ".update";

FaultTolerance.try_catch_with_logging_exception(
() =>
{
var xmlSerializer = new XmlSerializer(typeof(XmlType));
//var textWriter = new StreamWriter(xmlUpdateFilePath, append: false, encoding: new UTF8Encoding(encoderShouldEmitUTF8Identifier: false))
var textWriter = new StreamWriter(xmlUpdateFilePath, append: false, encoding: new UTF8Encoding(encoderShouldEmitUTF8Identifier: true))
// Write the updated file to memory
using(var memoryStream = new MemoryStream())
using(var streamWriter = new StreamWriter(memoryStream, encoding: new UTF8Encoding(encoderShouldEmitUTF8Identifier: true)))
{
AutoFlush = true
};
xmlSerializer.Serialize(streamWriter, xmlType);
streamWriter.Flush();
xmlSerializer.Serialize(textWriter, xmlType);
textWriter.Flush();
memoryStream.Position = 0;
// Grab the hash of both files and compare them.
var originalFileHash = _hashProvider.hash_file(xmlFilePath);
if (!originalFileHash.is_equal_to(_hashProvider.hash_stream(memoryStream)))
{
// If there wasn't a file there in the first place, just write the new one out directly.
if(string.IsNullOrEmpty(originalFileHash))
{
using(var updateFileStream = _fileSystem.create_file(xmlFilePath))
{
memoryStream.Position = 0;
memoryStream.CopyTo(updateFileStream);
textWriter.Close();
textWriter.Dispose();
return;
}
}
if (!_hashProvider.hash_file(xmlFilePath).is_equal_to(_hashProvider.hash_file(xmlUpdateFilePath)))
{
_fileSystem.copy_file(xmlUpdateFilePath, xmlFilePath, overwriteExisting: true);
// Otherwise, create an update file, and resiliently move it into place.
var tempUpdateFile = xmlFilePath + ".update";
using(var updateFileStream = _fileSystem.create_file(tempUpdateFile))
{
memoryStream.Position = 0;
memoryStream.CopyTo(updateFileStream);
}
_fileSystem.replace_file(tempUpdateFile, xmlFilePath, xmlFilePath + ".backup");
}
}
_fileSystem.delete_file(xmlUpdateFilePath);
},
"Error serializing type {0}".format_with(typeof(XmlType)),
throwError: true,
Expand Down

0 comments on commit eec3fd6

Please sign in to comment.