mirror of
https://github.com/velopack/velopack.git
synced 2025-10-25 15:19:22 +00:00
278 lines
10 KiB
C#
278 lines
10 KiB
C#
using System.Diagnostics.Contracts;
|
|
using System.Runtime.Serialization;
|
|
using System.Text.RegularExpressions;
|
|
|
|
namespace Velopack.Tests.OldSquirrel;
|
|
|
|
[DataContract]
|
|
public class ReleaseEntry
|
|
{
|
|
[DataMember] public string SHA1 { get; protected set; }
|
|
[DataMember] public string BaseUrl { get; protected set; }
|
|
[DataMember] public string Filename { get; protected set; }
|
|
[DataMember] public string Query { get; protected set; }
|
|
[DataMember] public long Filesize { get; protected set; }
|
|
[DataMember] public bool IsDelta { get; protected set; }
|
|
[DataMember] public float? StagingPercentage { get; protected set; }
|
|
|
|
protected ReleaseEntry(string sha1, string filename, long filesize, bool isDelta, string baseUrl = null, string query = null, float? stagingPercentage = null)
|
|
{
|
|
Contract.Requires(sha1 != null && sha1.Length == 40);
|
|
Contract.Requires(filename != null);
|
|
Contract.Requires(filename.Contains(Path.DirectorySeparatorChar) == false);
|
|
Contract.Requires(filesize > 0);
|
|
|
|
SHA1 = sha1; BaseUrl = baseUrl; Filename = filename; Query = query; Filesize = filesize; IsDelta = isDelta; StagingPercentage = stagingPercentage;
|
|
}
|
|
|
|
[IgnoreDataMember]
|
|
public string EntryAsString {
|
|
get {
|
|
if (StagingPercentage != null) {
|
|
return String.Format("{0} {1}{2} {3} # {4}", SHA1, BaseUrl, Filename, Filesize, stagingPercentageAsString(StagingPercentage.Value));
|
|
} else {
|
|
return String.Format("{0} {1}{2} {3}", SHA1, BaseUrl, Filename, Filesize);
|
|
}
|
|
}
|
|
}
|
|
|
|
[IgnoreDataMember]
|
|
public SemanticVersion Version { get { return Filename.ToSemanticVersion(); } }
|
|
|
|
static readonly Regex packageNameRegex = new Regex(@"^([\w-]+)-\d+\..+\.nupkg$");
|
|
[IgnoreDataMember]
|
|
public string PackageName {
|
|
get {
|
|
var match = packageNameRegex.Match(Filename);
|
|
return match.Success ?
|
|
match.Groups[1].Value :
|
|
Filename.Substring(0, Filename.IndexOfAny(new[] { '-', '.' }));
|
|
}
|
|
}
|
|
|
|
//public string GetReleaseNotes(string packageDirectory)
|
|
//{
|
|
// var zp = new ZipPackage(Path.Combine(packageDirectory, Filename));
|
|
// var t = zp.Id;
|
|
|
|
// if (String.IsNullOrWhiteSpace(zp.ReleaseNotes)) {
|
|
// throw new Exception(String.Format("Invalid 'ReleaseNotes' value in nuspec file at '{0}'", Path.Combine(packageDirectory, Filename)));
|
|
// }
|
|
|
|
// return zp.ReleaseNotes;
|
|
//}
|
|
|
|
//public Uri GetIconUrl(string packageDirectory)
|
|
//{
|
|
// var zp = new ZipPackage(Path.Combine(packageDirectory, Filename));
|
|
// return zp.IconUrl;
|
|
//}
|
|
|
|
static readonly Regex entryRegex = new Regex(@"^([0-9a-fA-F]{40})\s+(\S+)\s+(\d+)[\r]*$");
|
|
static readonly Regex commentRegex = new Regex(@"\s*#.*$");
|
|
static readonly Regex stagingRegex = new Regex(@"#\s+(\d{1,3})%$");
|
|
public static ReleaseEntry ParseReleaseEntry(string entry)
|
|
{
|
|
Contract.Requires(entry != null);
|
|
|
|
float? stagingPercentage = null;
|
|
var m = stagingRegex.Match(entry);
|
|
if (m != null && m.Success) {
|
|
stagingPercentage = Single.Parse(m.Groups[1].Value) / 100.0f;
|
|
}
|
|
|
|
entry = commentRegex.Replace(entry, "");
|
|
if (String.IsNullOrWhiteSpace(entry)) {
|
|
return null;
|
|
}
|
|
|
|
m = entryRegex.Match(entry);
|
|
if (!m.Success) {
|
|
throw new Exception("Invalid release entry: " + entry);
|
|
}
|
|
|
|
if (m.Groups.Count != 4) {
|
|
throw new Exception("Invalid release entry: " + entry);
|
|
}
|
|
|
|
string filename = m.Groups[2].Value;
|
|
|
|
// Split the base URL and the filename if an URI is provided,
|
|
// throws if a path is provided
|
|
string baseUrl = null;
|
|
string query = null;
|
|
|
|
if (Utility.IsHttpUrl(filename)) {
|
|
var uri = new Uri(filename);
|
|
var path = uri.LocalPath;
|
|
var authority = uri.GetLeftPart(UriPartial.Authority);
|
|
|
|
if (String.IsNullOrEmpty(path) || String.IsNullOrEmpty(authority)) {
|
|
throw new Exception("Invalid URL");
|
|
}
|
|
|
|
var indexOfLastPathSeparator = path.LastIndexOf("/") + 1;
|
|
baseUrl = authority + path.Substring(0, indexOfLastPathSeparator);
|
|
filename = path.Substring(indexOfLastPathSeparator);
|
|
|
|
if (!String.IsNullOrEmpty(uri.Query)) {
|
|
query = uri.Query;
|
|
}
|
|
}
|
|
|
|
if (filename.IndexOfAny(Path.GetInvalidFileNameChars()) > -1) {
|
|
throw new Exception("Filename can either be an absolute HTTP[s] URL, *or* a file name");
|
|
}
|
|
|
|
long size = Int64.Parse(m.Groups[3].Value);
|
|
bool isDelta = filenameIsDeltaFile(filename);
|
|
|
|
return new ReleaseEntry(m.Groups[1].Value, filename, size, isDelta, baseUrl, query, stagingPercentage);
|
|
}
|
|
|
|
public bool IsStagingMatch(Guid? userId)
|
|
{
|
|
// A "Staging match" is when a user falls into the affirmative
|
|
// bucket - i.e. if the staging is at 10%, this user is the one out
|
|
// of ten case.
|
|
if (!StagingPercentage.HasValue) return true;
|
|
if (!userId.HasValue) return false;
|
|
|
|
uint val = BitConverter.ToUInt32(userId.Value.ToByteArray(), 12);
|
|
|
|
double percentage = ((double) val / (double) UInt32.MaxValue);
|
|
return percentage < StagingPercentage.Value;
|
|
}
|
|
|
|
public static IEnumerable<ReleaseEntry> ParseReleaseFile(string fileContents)
|
|
{
|
|
if (String.IsNullOrEmpty(fileContents)) {
|
|
return new ReleaseEntry[0];
|
|
}
|
|
|
|
//fileContents = Utility.RemoveByteOrderMarkerIfPresent(fileContents);
|
|
|
|
var ret = fileContents.Split('\n')
|
|
.Where(x => !String.IsNullOrWhiteSpace(x))
|
|
.Select(ParseReleaseEntry)
|
|
.Where(x => x != null)
|
|
.ToArray();
|
|
|
|
return ret.Any(x => x == null) ? null : ret;
|
|
}
|
|
|
|
public static IEnumerable<ReleaseEntry> ParseReleaseFileAndApplyStaging(string fileContents, Guid? userToken)
|
|
{
|
|
if (String.IsNullOrEmpty(fileContents)) {
|
|
return new ReleaseEntry[0];
|
|
}
|
|
|
|
//fileContents = Utility.RemoveByteOrderMarkerIfPresent(fileContents);
|
|
|
|
var ret = fileContents.Split('\n')
|
|
.Where(x => !String.IsNullOrWhiteSpace(x))
|
|
.Select(ParseReleaseEntry)
|
|
.Where(x => x != null && x.IsStagingMatch(userToken))
|
|
.ToArray();
|
|
|
|
return ret.Any(x => x == null) ? null : ret;
|
|
}
|
|
|
|
|
|
//public static void WriteReleaseFile(IEnumerable<ReleaseEntry> releaseEntries, Stream stream)
|
|
//{
|
|
// Contract.Requires(releaseEntries != null && releaseEntries.Any());
|
|
// Contract.Requires(stream != null);
|
|
|
|
// using (var sw = new StreamWriter(stream, Encoding.UTF8)) {
|
|
// sw.Write(String.Join("\n", releaseEntries
|
|
// .OrderBy(x => x.Version)
|
|
// .ThenByDescending(x => x.IsDelta)
|
|
// .Select(x => x.EntryAsString)));
|
|
// }
|
|
//}
|
|
|
|
//public static void WriteReleaseFile(IEnumerable<ReleaseEntry> releaseEntries, string path)
|
|
//{
|
|
// Contract.Requires(releaseEntries != null && releaseEntries.Any());
|
|
// Contract.Requires(!String.IsNullOrEmpty(path));
|
|
|
|
// using (var f = File.Open(path, FileMode.Create, FileAccess.Write, FileShare.None)) {
|
|
// WriteReleaseFile(releaseEntries, f);
|
|
// }
|
|
//}
|
|
|
|
//public static ReleaseEntry GenerateFromFile(Stream file, string filename, string baseUrl = null)
|
|
//{
|
|
// Contract.Requires(file != null && file.CanRead);
|
|
// Contract.Requires(!String.IsNullOrEmpty(filename));
|
|
|
|
// var hash = Utility.CalculateStreamSHA1(file);
|
|
// return new ReleaseEntry(hash, filename, file.Length, filenameIsDeltaFile(filename), baseUrl);
|
|
//}
|
|
|
|
//public static ReleaseEntry GenerateFromFile(string path, string baseUrl = null)
|
|
//{
|
|
// using (var inf = File.OpenRead(path)) {
|
|
// return GenerateFromFile(inf, Path.GetFileName(path), baseUrl);
|
|
// }
|
|
//}
|
|
|
|
//public static List<ReleaseEntry> BuildReleasesFile(string releasePackagesDir)
|
|
//{
|
|
// var packagesDir = new DirectoryInfo(releasePackagesDir);
|
|
|
|
// // Generate release entries for all of the local packages
|
|
// var entriesQueue = new ConcurrentQueue<ReleaseEntry>();
|
|
// Parallel.ForEach(packagesDir.GetFiles("*.nupkg"), x => {
|
|
// using (var file = x.OpenRead()) {
|
|
// entriesQueue.Enqueue(GenerateFromFile(file, x.Name));
|
|
// }
|
|
// });
|
|
|
|
// // Write the new RELEASES file to a temp file then move it into
|
|
// // place
|
|
// var entries = entriesQueue.ToList();
|
|
// var tempFile = default(string);
|
|
// Utility.WithTempFile(out tempFile, releasePackagesDir);
|
|
|
|
// try {
|
|
// using (var of = File.OpenWrite(tempFile)) {
|
|
// if (entries.Count > 0) WriteReleaseFile(entries, of);
|
|
// }
|
|
|
|
// var target = Path.Combine(packagesDir.FullName, "RELEASES");
|
|
// if (File.Exists(target)) {
|
|
// File.Delete(target);
|
|
// }
|
|
|
|
// File.Move(tempFile, target);
|
|
// } finally {
|
|
// if (File.Exists(tempFile)) Utility.DeleteFileHarder(tempFile, true);
|
|
// }
|
|
|
|
// return entries;
|
|
//}
|
|
|
|
static string stagingPercentageAsString(float percentage)
|
|
{
|
|
return String.Format("{0:F0}%", percentage * 100.0);
|
|
}
|
|
|
|
static bool filenameIsDeltaFile(string filename)
|
|
{
|
|
return filename.EndsWith("-delta.nupkg", StringComparison.InvariantCultureIgnoreCase);
|
|
}
|
|
|
|
//public static ReleasePackage GetPreviousRelease(IEnumerable<ReleaseEntry> releaseEntries, IReleasePackage package, string targetDir)
|
|
//{
|
|
// if (releaseEntries == null || !releaseEntries.Any()) return null;
|
|
|
|
// return releaseEntries
|
|
// .Where(x => x.IsDelta == false)
|
|
// .Where(x => x.Version < package.ToSemanticVersion())
|
|
// .OrderByDescending(x => x.Version)
|
|
// .Select(x => new ReleasePackage(Path.Combine(targetDir, x.Filename), true))
|
|
// .FirstOrDefault();
|
|
//}
|
|
} |