wasStitchNET

Subversion Repositories:
Compare Path: Rev
With Path: Rev
?path1? @ 12  →  ?path2? @ 13
/Repository/Files.cs
@@ -0,0 +1,139 @@
///////////////////////////////////////////////////////////////////////////
// Copyright (C) Wizardry and Steamworks 2017 - License: GNU GPLv3 //
// Please see: http://www.gnu.org/licenses/gpl.html for legal details, //
// rights of fair usage, the disclaimer and warranty conditions. //
///////////////////////////////////////////////////////////////////////////
 
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Threading.Tasks;
using wasDAVClient;
using wasSharp;
using wasSharp.Linq;
using wasSharp.Web.Utilities;
using wasSharpNET.IO.Utilities;
using wasStitchNET.Structures;
 
namespace wasStitchNET.Repository
{
public class Files
{
/// <summary>
/// Load all files off a remote repository and return them as a stitchable file.
/// </summary>
/// <param name="client">the wasDAVClient to use</param>
/// <param name="path">the web path to retrieve the files from</param>
/// <param name="basePath">the base path of the repository</param>
/// <param name="pathSeparator">the DAV separator character</param>
/// <returns>a collection of stitch files</returns>
public static IEnumerable<StitchFile> LoadRemoteFiles(Client client, string path, string basePath,
char pathSeparator = '/')
{
Func<string, Task<StitchFile>> getStitchFile = async file =>
{
/*using (var memoryStream = new MemoryStream())
{
using (var stream = await client.Download(file))
{
await stream.CopyToAsync(memoryStream);
}
return new StitchFile
{
Path =
file.PathSplit(pathSeparator)
.Select(WebExtensions.URLUnescapeDataString)
.SequenceExcept(
basePath.PathSplit(pathSeparator)),
SHA1 = SHA1.Create().ToHex(memoryStream.ToArray()),
PathType = StitchPathType.PATH_FILE
};
}*/
using (var sha1managed = new SHA1Managed())
{
using (var stream = await client.Download(file))
{
//fileHash = sha1managed.ComputeHash(stream);
return new StitchFile
{
Path =
file.PathSplit(pathSeparator)
.Select(WebExtensions.URLUnescapeDataString)
.SequenceExcept(
basePath.PathSplit(pathSeparator)),
SHA1 = sha1managed.ComputeHash(stream).ToHexString(),
PathType = StitchPathType.PATH_FILE
};
}
}
};
 
foreach (var item in client.List(path).Result)
switch (item.IsCollection)
{
case true:
var directoryPath = item.Href.TrimEnd(pathSeparator).PathSplit(pathSeparator)
.Select(WebExtensions.URLUnescapeDataString)
.SequenceExcept(
basePath.PathSplit(pathSeparator));
yield return new StitchFile
{
Path = directoryPath,
SHA1 = string.Empty,
PathType = StitchPathType.PATH_DIRECTORY
};
foreach (var file in LoadRemoteFiles(client, item.Href, basePath))
yield return file;
break;
 
default:
yield return getStitchFile(item.Href).Result;
break;
}
}
 
/// <summary>
/// Load all files from a local path and return them as a stitchable file.
/// </summary>
/// <param name="path">the local path to files</param>
/// <param name="basePath">the base path to the folder</param>
/// <param name="pathSeparator">the local filesystem separator to use</param>
/// <returns>a collection of stitch files</returns>
public static IEnumerable<StitchFile> LoadLocalFiles(string path, string basePath, char pathSeparator = '\\')
{
foreach (var file in Directory.GetFiles(path))
using (var sha1managed = new SHA1Managed())
{
using (var fileStream = IOExtensions.GetWriteStream(file, FileMode.Open,
FileAccess.Read, FileShare.Read, STITCH_CONSTANTS.LOCAL_FILE_ACCESS_TIMEOUT))
{
yield return new StitchFile
{
Path =
file.PathSplit(pathSeparator)
.SequenceExcept(
basePath.PathSplit(pathSeparator)),
SHA1 = sha1managed.ComputeHash(fileStream).ToHexString(),
PathType = StitchPathType.PATH_FILE
};
}
}
foreach (var directory in Directory.GetDirectories(path))
{
var directoryPath = directory.PathSplit(pathSeparator)
.SequenceExcept(
basePath.PathSplit(pathSeparator));
yield return new StitchFile
{
Path = directoryPath,
SHA1 = string.Empty,
PathType = StitchPathType.PATH_DIRECTORY
};
foreach (var file in LoadLocalFiles(directory, basePath, pathSeparator))
yield return file;
}
}
}
}
/Repository/Hashing.cs
@@ -0,0 +1,108 @@
///////////////////////////////////////////////////////////////////////////
// Copyright (C) Wizardry and Steamworks 2016 - License: GNU GPLv3 //
// Please see: http://www.gnu.org/licenses/gpl.html for legal details, //
// rights of fair usage, the disclaimer and warranty conditions. //
///////////////////////////////////////////////////////////////////////////
 
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Threading.Tasks;
using wasDAVClient;
using wasSharp;
using wasSharpNET.IO.Utilities;
 
namespace wasStitchNET.Repository
{
public class Hashing
{
/// <summary>
/// Hash local files inside a directory.
/// </summary>
/// <param name="path">the path to a directory to hash</param>
/// <param name="bufferSize">the partial hash buffer (default: 16384)</param>
/// <returns>a SHA1 hash of all files in a directory ordered by name</returns>
public static async Task<string> HashLocalFiles(string path, int bufferSize = 16384)
{
return await new Task<string>(() =>
{
using (var sha1managed = new SHA1Managed())
{
foreach (var stream in Directory
.GetFiles(path, "*.*", SearchOption.AllDirectories)
.OrderBy(o => o.Split(Path.DirectorySeparatorChar).Last())
.AsParallel()
.AsOrdered()
.Select(file => IOExtensions.GetWriteStream(file, FileMode.Open,
FileAccess.Read, FileShare.Read, STITCH_CONSTANTS.LOCAL_FILE_ACCESS_TIMEOUT))
.AsSequential())
using (var binaryReader = new BinaryReader(stream))
{
var buff = new byte[bufferSize];
int read;
while ((read = binaryReader.Read(buff, 0, buff.Length)) != 0)
sha1managed.TransformBlock(buff, 0, read, null, 0);
}
 
sha1managed.TransformFinalBlock(new byte[] { }, 0, 0);
return sha1managed.Hash.ToHexString();
}
});
}
 
/// <summary>
/// Hash the files of a Stitch remote repository release.
/// </summary>
/// <param name="server">the server to hash the release for</param>
/// <param name="update">the Stitch release to hash</param>
/// <param name="timeout">the timeout in milliseconds to allow the server to respond</param>
/// <returns>a SHA1 hash of all files in the release directory ordered by name</returns>
public static async Task<string> HashRemoteFiles(string server, string update, int timeout)
{
using (var client = new Client(new NetworkCredential())
{
Timeout = timeout,
UserAgent = STITCH_CONSTANTS.USER_AGENT.Product.Name,
UserAgentVersion = STITCH_CONSTANTS.USER_AGENT.Product.Version,
Server = server,
BasePath = string.Join(@"/", STITCH_CONSTANTS.UPDATE_PATH,
STITCH_CONSTANTS.PROGRESSIVE_PATH)
})
{
return $"{await HashRemoteFiles(client, string.Join(@"/", update, STITCH_CONSTANTS.UPDATE_DATA_PATH))}";
}
}
 
/// <summary>
/// Hash the files of a Stitch remote repository release using an existing was DAV client.
/// </summary>
/// <param name="client">the was DAV client to use</param>
/// <param name="path">the path to the repository release folder</param>
/// <param name="bufferSize">the partial hash buffer (default: 16384)</param>
/// <returns>a SHA1 hash of all files in the release directory ordered by name</returns>
public static async Task<string> HashRemoteFiles(Client client, string path, int bufferSize = 16384)
{
using (var sha1managed = new SHA1Managed())
{
foreach (var stream in (await client.List(path, Constants.DavDepth.ALL))
.OrderBy(o => o.DisplayName)
.Where(item => !item.IsCollection)
.AsParallel()
.AsOrdered()
.Select(file => client.Download(file.Href))
.AsSequential())
using (var binaryReader = new BinaryReader(await stream))
{
var buff = new byte[bufferSize];
int read;
while ((read = binaryReader.Read(buff, 0, buff.Length)) != 0)
sha1managed.TransformBlock(buff, 0, read, null, 0);
}
 
sha1managed.TransformFinalBlock(new byte[] { }, 0, 0);
return sha1managed.Hash.ToHexString();
}
}
}
}
/Repository/Mirrors.cs
@@ -0,0 +1,143 @@
///////////////////////////////////////////////////////////////////////////
// Copyright (C) Wizardry and Steamworks 2017 - License: GNU GPLv3 //
// Please see: http://www.gnu.org/licenses/gpl.html for legal details, //
// rights of fair usage, the disclaimer and warranty conditions. //
///////////////////////////////////////////////////////////////////////////
 
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using MaxMind.GeoIP2.Responses;
using wasDAVClient;
using wasSharp.Geo;
using wasStitchNET.GeoIP;
using wasStitchNET.Structures;
 
namespace wasStitchNET.Repository
{
public static class Mirrors
{
/// <summary>
/// Returns a list of valid Stitch mirrors.
/// </summary>
/// <param name="localCity">the local city from where the method is invoked</param>
/// <param name="mirror">the mirror to get the distance to</param>
/// <param name="client">the was DAV client to use</param>
/// <param name="server">the Stitch server to query</param>
/// <returns>a list of discovered Stitch mirrors</returns>
public static IEnumerable<StitchMirror> InitializeMirrors(CityResponse localCity, Client client, string server)
{
Func<string, StitchMirror> computeMirrorDistance = o =>
{
var mirrorDistance = GetMirrorDistance(localCity, o);
if (mirrorDistance.Equals(default(KeyValuePair<string, Distance>)))
return default(StitchMirror);
return new StitchMirror(mirrorDistance.Key, mirrorDistance.Value);
};
 
var mirrors = new List<string> {server};
yield return computeMirrorDistance(server);
 
foreach (var stitchMirror in mirrors.AsParallel().SelectMany(
mirror => FetchStitchMirrors(client, mirror)
.AsParallel()
.Where(o => !string.IsNullOrEmpty(o) && !mirrors.Contains(o))
.Select(computeMirrorDistance)))
{
if (stitchMirror.Equals(default(StitchMirror)))
continue;
mirrors.Add(stitchMirror.Address);
yield return stitchMirror;
}
}
 
/// <summary>
/// Retrieves a list of mirrors from a Stitch repository.
/// </summary>
/// <param name="client">the was DAV client to use</param>
/// <param name="server">the Stitch server to retrieve the mirrors from</param>
/// <returns>a list of mirrors</returns>
public static IEnumerable<string> FetchStitchMirrors(Client client, string server)
{
// Set the server.
client.Server = server;
 
// Set the mirror path
var mirrorsPath = @"/" +
string.Join(@"/", STITCH_CONSTANTS.UPDATE_PATH, STITCH_CONSTANTS.PROGRESSIVE_PATH,
STITCH_CONSTANTS.UPDATE_MIRRORS_FILE);
 
using (var stream = client.Download(mirrorsPath).Result)
{
if (stream == null)
yield break;
 
using (var streamReader = new StreamReader(stream))
{
string mirror;
do
{
mirror = streamReader.ReadLine();
 
if (string.IsNullOrEmpty(mirror))
continue;
 
yield return mirror;
} while (!string.IsNullOrEmpty(mirror));
}
}
}
 
/// <summary>
/// Gets the distance to a mirror.
/// </summary>
/// <param name="localCity">the local city from where the method is invoked</param>
/// <param name="mirror">the mirror to get the distance to</param>
/// <returns>a key-value pair of mirror by distance</returns>
public static KeyValuePair<string, Distance> GetMirrorDistance(CityResponse localCity, string mirror)
{
// Check that the mirror has a proper URI.
Uri mirrorUri;
if (!Uri.TryCreate(mirror, UriKind.Absolute, out mirrorUri))
return default(KeyValuePair<string, Distance>);
 
// If we do not know the local city, then just return the mirror.
if (localCity == null)
return new KeyValuePair<string, Distance>(mirror, null);
 
// Resolve the mirror hostname to an IP address.
IPAddress address;
try
{
address = Dns.GetHostAddresses(mirrorUri.Host).FirstOrDefault();
}
catch (Exception)
{
return
new KeyValuePair<string, Distance>(mirror, null);
}
 
// Resolve the IP address to a city response.
var remoteCity = address.GeoIPGetCity();
if (remoteCity == null)
return new KeyValuePair<string, Distance>(mirror, null);
 
// Compute the distance to the mirror.
switch (remoteCity.Location.HasCoordinates)
{
case true:
var local = new GeographicCoordinate(localCity.Location.Latitude.Value,
localCity.Location.Longitude.Value);
var remote = new GeographicCoordinate(remoteCity.Location.Latitude.Value,
remoteCity.Location.Longitude.Value);
return
new KeyValuePair<string, Distance>(mirror, local.HaversineDistanceTo(remote));
default:
return
new KeyValuePair<string, Distance>(mirror, null);
}
}
}
}
/Repository/StitchOptions.cs
@@ -0,0 +1,82 @@
///////////////////////////////////////////////////////////////////////////
// Copyright (C) Wizardry and Steamworks 2013 - License: GNU GPLv3 //
// Please see: http://www.gnu.org/licenses/gpl.html for legal details, //
// rights of fair usage, the disclaimer and warranty conditions. //
///////////////////////////////////////////////////////////////////////////
 
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;
using wasSharp;
 
namespace wasStitchNET.Repository
{
[XmlRoot(ElementName = "FileExcludes")]
public class FileExcludes
{
[XmlElement(ElementName = "Path")]
public HashSet<string> Path { get; set; }
}
 
[XmlRoot(ElementName = "ConfigurationExcludes")]
public class ConfigurationExcludes
{
[XmlElement(ElementName = "Path")]
public HashSet<string> Tag { get; set; }
}
 
[XmlRoot(ElementName = "ConfigurationForce")]
public class ConfigurationForce
{
[XmlElement(ElementName = "Path")]
public HashSet<string> Tag { get; set; }
}
 
[XmlRoot(ElementName = "StitchOptions")]
public class StitchOptions
{
[Reflection.NameAttribute("force")]
[XmlElement(ElementName = "force")]
public bool Force { get; set; }
 
[Reflection.NameAttribute("FileExcludes")]
[XmlElement(ElementName = "FileExcludes")]
public FileExcludes FileExcludes { get; set; }
 
[Reflection.NameAttribute("ConfigurationExcludes")]
[XmlElement(ElementName = "ConfigurationExcludes")]
public ConfigurationExcludes ConfigurationExcludes { get; set; }
 
[Reflection.NameAttribute("ConfigurationForce")]
[XmlElement(ElementName = "ConfigurationForce")]
public ConfigurationExcludes ConfigurationForce { get; set; }
 
[XmlAttribute(AttributeName = "xsi", Namespace = "http://www.w3.org/2000/xmlns/")]
public string Xsi { get; set; }
 
[XmlAttribute(AttributeName = "xsd", Namespace = "http://www.w3.org/2000/xmlns/")]
public string Xsd { get; set; }
}
 
public static class StitchOptionsSerialization
{
public static StitchOptions Load(this StitchOptions stitchOptions, Stream stream)
{
using (var streamReader = new StreamReader(stream))
{
var serializer = new XmlSerializer(typeof(StitchOptions));
return (StitchOptions) serializer.Deserialize(streamReader);
}
}
 
public static string ToXML(this StitchOptions stitchOptions)
{
using (var writer = new StringWriter())
{
var serializer = new XmlSerializer(typeof(StitchOptions));
serializer.Serialize(writer, stitchOptions);
return writer.ToString();
}
}
}
}
/Repository/Stitching/StitchProgressEvent.cs
@@ -0,0 +1,20 @@
///////////////////////////////////////////////////////////////////////////
// Copyright (C) Wizardry and Steamworks 2017 - License: GNU GPLv3 //
// Please see: http://www.gnu.org/licenses/gpl.html for legal details, //
// rights of fair usage, the disclaimer and warranty conditions. //
///////////////////////////////////////////////////////////////////////////
 
using System;
 
namespace wasStitchNET.Repository.Stitching
{
public class StitchProgressEventArgs : EventArgs
{
public StitchProgressEventArgs(string status)
{
Status = status;
}
 
public string Status { get; }
}
}
/Repository/Stitching/Stitching.cs
@@ -0,0 +1,447 @@
///////////////////////////////////////////////////////////////////////////
// Copyright (C) Wizardry and Steamworks 2017 - License: GNU GPLv3 //
// Please see: http://www.gnu.org/licenses/gpl.html for legal details, //
// rights of fair usage, the disclaimer and warranty conditions. //
///////////////////////////////////////////////////////////////////////////
 
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml.Linq;
using wasDAVClient;
using wasSharp;
using wasSharp.Linq;
using wasSharpNET.IO.Utilities;
using wasStitchNET.Structures;
using XML = wasStitchNET.Patchers.XML;
 
namespace wasStitchNET.Repository.Stitching
{
public class Stitching
{
/// <summary>
/// Delegate to subscribe to for stitch progress events.
/// </summary>
/// <param name="sender">the sender</param>
/// <param name="e">stitch progress event arguments</param>
public delegate void StitchProgressEventHandler(object sender, StitchProgressEventArgs e);
 
/// <summary>
/// Stitch progress event handler.
/// </summary>
public event StitchProgressEventHandler OnProgressUpdate;
 
/// <summary>
/// Commodity method to raise stitching progress events.
/// </summary>
/// <param name="status">the current stitching status</param>
private void StitchProgressUpdate(string status)
{
// Make sure someone is listening to event
if (OnProgressUpdate == null) return;
 
var args = new StitchProgressEventArgs(status);
OnProgressUpdate(this, args);
}
 
/// <summary>
/// Stitch!
/// </summary>
/// <param name="client">the was DAV client to use</param>
/// <param name="server">the repository URL to stitch from</param>
/// <param name="version">the release to stitch to</param>
/// <param name="path">the path to the local files to be stitched</param>
/// <param name="nopatch">true if files should not be patched</param>
/// <param name="clean">whether to perform a clean stitching by removing local files</param>
/// <param name="force">whether to force stitching repository paths</param>
/// <param name="noverify">true if remote files should not be checked against official checksum</param>
/// <param name="dryrun">whether to perform a dryrun run operation without making any changes</param>
/// <returns>true if stitching completed successfully</returns>
public async Task<bool> Stitch(Client client, string server, string version, string path,
bool nopatch = false,
bool clean = false, bool force = false, bool noverify = false, bool dryrun = false)
{
// Set the server.
client.Server = server;
 
// The repository path to the version to update.
var updateVersionPath = @"/" +
string.Join(@"/", STITCH_CONSTANTS.UPDATE_PATH,
STITCH_CONSTANTS.PROGRESSIVE_PATH,
version);
// Check that the repository has the requested version.
StitchProgressUpdate("Attempting to retrieve remote repository update version folder.");
try
{
if (!client.GetFolder(updateVersionPath).Result.IsCollection)
throw new Exception();
}
catch (Exception)
{
throw new StitchException("The repository does not have requested version available.");
}
 
// The repository path to the checksum file of the version to update.
var updateChecksumPath = string.Join(@"/", updateVersionPath, STITCH_CONSTANTS.UPDATE_CHECKSUM_FILE);
 
// Attempt to retrieve remote checksum file and normalize the hash.
StitchProgressUpdate("Retrieving remote repository checksum file.");
string updateChecksum;
try
{
using (var stream = client.Download(updateChecksumPath).Result)
{
using (var reader = new StreamReader(stream))
{
// Trim any spaces since we only care about a single-line hash.
updateChecksum = Regex.Replace(reader.ReadToEnd(), @"\s+", string.Empty);
}
}
}
catch (Exception ex)
{
throw new StitchException("Unable to retrieve repository checksum file.", ex);
}
 
if (string.IsNullOrEmpty(updateChecksum))
throw new StitchException("Empty repository update checksum.");
 
// Hash the remote repository files.
StitchProgressUpdate("Hashing remote repository checksum files.");
string remoteChecksum;
try
{
remoteChecksum = await Hashing.HashRemoteFiles(client,
string.Join(@"/", version, STITCH_CONSTANTS.UPDATE_DATA_PATH));
}
catch (Exception ex)
{
throw new StitchException("Unable to compute remote checksum.", ex);
}
 
if (string.IsNullOrEmpty(remoteChecksum))
throw new StitchException("Empty remote checksum.");
 
// Check that the repository checksum file matches the repository file hash.
StitchProgressUpdate("Comparing remote repository checksum against remote repository files checksum.");
if (!string.Equals(updateChecksum, remoteChecksum, StringComparison.OrdinalIgnoreCase))
throw new StitchException("Repository file checksum mismatch.");
 
// Check that the computed repository file checksum matches the official repository checksum file.
if (!noverify)
{
StitchProgressUpdate("Preparing to verify remote repository file checksum to official checksum.");
 
// Retrieve the official repository checksum file for the requested stitch version.
StitchProgressUpdate("Retrieving official repository checksum for requested release version.");
string officialChecksum;
try
{
// Point the server to the official server.
client.Server = STITCH_CONSTANTS.OFFICIAL_UPDATE_SERVER;
using (var stream = client.Download(updateChecksumPath).Result)
{
using (var reader = new StreamReader(stream))
{
// Trim any spaces since we only care about a single-line hash.
officialChecksum = Regex.Replace(reader.ReadToEnd(), @"\s+", string.Empty);
}
}
}
catch (Exception ex)
{
throw new StitchException("Unable to retrieve official repository checksum file.", ex);
}
finally
{
client.Server = server;
}
 
if (string.IsNullOrEmpty(officialChecksum))
throw new StitchException("Unable to retrieve official repository checksum file.");
 
// Compare the official checksum to the repository file checksum.
StitchProgressUpdate(
$"Comparing official repository checksum ({officialChecksum}) against remote repository files checksum ({remoteChecksum}).");
if (!string.Equals(officialChecksum, remoteChecksum, StringComparison.OrdinalIgnoreCase))
throw new StitchException("Repository file checksum does not match official repository checksum.");
}
 
 
var stitchOptions = new StitchOptions();
var optionsPath = @"/" +
string.Join(@"/", STITCH_CONSTANTS.UPDATE_PATH, STITCH_CONSTANTS.PROGRESSIVE_PATH,
version, STITCH_CONSTANTS.UPDATE_OPTIONS_FILE);
 
// Retrieve the repository upgrade options file.
StitchProgressUpdate("Retrieving remote repository options.");
try
{
using (var stream = client.Download(optionsPath).Result)
{
stitchOptions = stitchOptions.Load(stream);
}
}
catch (Exception ex)
{
throw new StitchException("Unable to retrieve repository options.", ex);
}
 
// Retrieve the remote repository release files.
StitchProgressUpdate("Retrieving remote repository release files.");
var remoteFiles = new HashSet<StitchFile>();
try
{
remoteFiles.UnionWith(
Files.LoadRemoteFiles(client,
string.Join(@"/", version, STITCH_CONSTANTS.UPDATE_DATA_PATH),
@"/" + string.Join(@"/", STITCH_CONSTANTS.UPDATE_PATH, STITCH_CONSTANTS.PROGRESSIVE_PATH,
version, STITCH_CONSTANTS.UPDATE_DATA_PATH)
));
}
catch (Exception ex)
{
throw new StitchException("Unable to download repository release files.", ex);
}
 
// Retrieve local path files.
StitchProgressUpdate("Retrieving local path files.");
var localFiles = new HashSet<StitchFile>();
try
{
localFiles.UnionWith(Files.LoadLocalFiles(path,
path,
Path.DirectorySeparatorChar));
}
catch (Exception ex)
{
throw new StitchException("Unable to load local files.", ex);
}
 
// Files to be wiped.
var wipeFiles = new HashSet<StitchFile>();
if (clean)
switch (stitchOptions.Force || force)
{
case true:
wipeFiles.UnionWith(localFiles.Except(remoteFiles));
break;
 
default:
wipeFiles.UnionWith(
localFiles.Except(remoteFiles)
.Where(
o =>
stitchOptions.FileExcludes.Path.All(
p =>
o.Path.SequenceExcept(p.PathSplit(Path.DirectorySeparatorChar))
.Count()
.Equals(o.Path.Count()))));
break;
}
 
// Files to be stitched.
var stitchFiles = new HashSet<StitchFile>();
// If the force option was specified then stitch all the files that are not in the remote
// repository by ignoring any excludes.
switch (stitchOptions.Force || force)
{
case true:
stitchFiles.UnionWith(remoteFiles.Except(localFiles));
break;
 
default:
stitchFiles.UnionWith(
remoteFiles.Except(localFiles)
.Where(
o =>
stitchOptions.FileExcludes.Path.All(
p =>
o.Path.SequenceExcept(p.PathSplit(Path.DirectorySeparatorChar))
.Count()
.Equals(o.Path.Count()))));
break;
}
 
// Wipe local files and directories that have to be removed.
StitchProgressUpdate("Removing local files and folders.");
var directories = new Queue<string>();
foreach (var file in wipeFiles)
{
var deletePath = string.Join(Path.DirectorySeparatorChar.ToString(),
path,
string.Join(Path.DirectorySeparatorChar.ToString(), file.Path));
try
{
switch (file.PathType)
{
case StitchPathType.PATH_FILE:
if (!dryrun)
File.Delete(deletePath);
break;
 
case StitchPathType.PATH_DIRECTORY: // we cannot delete the directories right away.
directories.Enqueue(deletePath);
break;
}
}
catch (Exception ex)
{
throw new StitchException("Unable remove local files.", ex);
}
}
 
directories = new Queue<string>(directories.OrderByDescending(o => o));
 
while (directories.Any())
{
var deletePath = directories.Dequeue();
try
{
if (!dryrun)
Directory.Delete(deletePath);
}
catch (Exception ex)
{
throw new StitchException("Unable remove local directories.", ex);
}
}
 
// Stitch files that have to be stitched.
StitchProgressUpdate("Stitching files.");
foreach (var file in stitchFiles)
try
{
var stitchRemotePath = @"/" + string.Join(@"/", STITCH_CONSTANTS.UPDATE_PATH,
STITCH_CONSTANTS.PROGRESSIVE_PATH,
version, STITCH_CONSTANTS.UPDATE_DATA_PATH,
string.Join("/", file.Path));
var stitchLocalPath = string.Join(Path.DirectorySeparatorChar.ToString(),
path.PathSplit(Path.DirectorySeparatorChar)
.Concat(file.Path));
 
switch (file.PathType)
{
case StitchPathType.PATH_DIRECTORY:
// Create the directory.
if (!dryrun)
Directory.CreateDirectory(stitchLocalPath);
continue;
case StitchPathType.PATH_FILE:
// Create the directory to the stitch file.
if (!dryrun)
Directory.CreateDirectory(
string.Join(Path.DirectorySeparatorChar.ToString(),
stitchLocalPath.PathSplit(Path.DirectorySeparatorChar).Reverse()
.Skip(1)
.Reverse()));
break;
}
 
using (var memoryStream = new MemoryStream())
{
using (var stream = client.Download(stitchRemotePath).Result)
{
stream.CopyTo(memoryStream);
}
memoryStream.Position = 0L;
if (!dryrun)
using (var fileStream =
IOExtensions.GetWriteStream(stitchLocalPath, FileMode.Create,
FileAccess.Write, FileShare.None, STITCH_CONSTANTS.LOCAL_FILE_ACCESS_TIMEOUT))
{
memoryStream.CopyTo(fileStream);
}
}
}
catch (Exception ex)
{
throw new StitchException("Unable to stitch files.", ex);
}
 
// If no file patches was requested then do not patch and the process is complete.
if (nopatch)
return true;
 
StitchProgressUpdate("Patching files.");
 
// Retrive working file.
var workingFilePath = string.Join(Path.DirectorySeparatorChar.ToString(),
path, @"Corrade.ini");
 
StitchProgressUpdate("Parsing working file to be patched.");
XDocument workingFile;
try
{
workingFile = XDocument.Load(workingFilePath);
}
catch (Exception ex)
{
throw new StitchException("Unable to parse working file to be patched.", ex);
}
 
// Retrieve default file.
StitchProgressUpdate("Parsing default file to be patched.");
XDocument defaultFile;
try
{
defaultFile = XDocument.Load(string.Join(Path.DirectorySeparatorChar.ToString(),
path, @"Corrade.ini.default"));
}
catch (Exception ex)
{
throw new StitchException("Unable to parse default file to be patched.", ex);
}
 
// XPaths to exclude from patching.
var excludeXPaths = new HashSet<string>();
if (stitchOptions.ConfigurationExcludes != null)
excludeXPaths.UnionWith(stitchOptions.ConfigurationExcludes.Tag);
 
// XPaths to force whilst patching.
var forceXPaths = new HashSet<string>();
if (stitchOptions.ConfigurationForce != null)
forceXPaths.UnionWith(stitchOptions.ConfigurationForce.Tag);
 
// Patch the file.
StitchProgressUpdate("Patching file.");
var patchedFile = XML
.PatchXDocument(workingFile, defaultFile, forceXPaths, excludeXPaths);
if (patchedFile == null)
throw new StitchException("Unable to patch XML files.");
 
// Create a backup for the file to be patched.
StitchProgressUpdate("Creating a backup of the file to be patched.");
try
{
if (!dryrun)
File.Copy(workingFilePath,
string.Join(Path.DirectorySeparatorChar.ToString(),
path, @"Corrade.ini.bak"), true);
}
catch (Exception ex)
{
throw new StitchException("Unable to create patched file backup.", ex);
}
 
// Write the patched file.
StitchProgressUpdate("Saving the patched file.");
try
{
if (!dryrun)
patchedFile.Save(string.Join(Path.DirectorySeparatorChar.ToString(),
path, @"Corrade.ini"));
}
catch (Exception ex)
{
throw new StitchException("Unable to save patched file.", ex);
}
 
StitchProgressUpdate("Stitching successful.");
return true;
}
}
}
/Repository/Tools.cs
@@ -15,6 +15,7 @@
using wasSharp.Linq;
using wasSharp.Web.Utilities;
using wasSharpNET.Cryptography;
using SHA1 = System.Security.Cryptography.SHA1;
 
namespace wasStitchNET.Repository
{
@@ -79,7 +80,8 @@
/// <param name="release">the release to list files for</param>
/// <param name="timeout">the timeout connecting to the Stitch repository</param>
/// <returns>a list of Corrade files contained in the repository</returns>
public static IEnumerable<KeyValuePair<string, string>> GetReleaseFileHashes(string server, Version release, int timeout)
public static IEnumerable<KeyValuePair<string, string>> GetReleaseFileHashes(string server, Version release,
int timeout)
{
try
{
@@ -116,7 +118,7 @@
$"{release.Major}.{release.Minor}",
STITCH_CONSTANTS.UPDATE_DATA_PATH
})),
o => System.Security.Cryptography.SHA1.Create().ToHex(client.Download(o.Href).Result));
o => SHA1.Create().ToHex(client.Download(o.Href).Result));
}
}
catch (wasDAVException ex)
@@ -125,4 +127,4 @@
}
}
}
}
}