public static ReleaseEntry ParseReleaseEntry(string entry) { Contract.Requires(entry != null); entry = commentRegex.Replace(entry, ""); if (String.IsNullOrWhiteSpace(entry)) { return(null); } var 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)); }
public async Task DownloadReleases(string updateUrlOrPath, IEnumerable <ReleaseEntry> releasesToDownload, Action <int> progress = null, IFileDownloader urlDownloader = null) { progress = progress ?? (_ => { }); urlDownloader = urlDownloader ?? new FileDownloader(); int current = 0; int toIncrement = (int)(100.0 / releasesToDownload.Count()); if (Utility.IsHttpUrl(updateUrlOrPath)) { await releasesToDownload.ForEachAsync(async x => { await urlDownloader.DownloadFile( String.Format("{0}/{1}", updateUrlOrPath, x.Filename), Path.Combine(rootAppDirectory, "packages", x.Filename)); lock (progress) progress(current += toIncrement); }); } else { await releasesToDownload.ForEachAsync(x => { File.Copy( Path.Combine(updateUrlOrPath, x.Filename), Path.Combine(rootAppDirectory, "packages", x.Filename)); lock (progress) progress(current += toIncrement); }); } }
public async Task DownloadReleases(string updateUrlOrPath, IEnumerable <ReleaseEntry> releasesToDownload, Action <int> progress = null, IFileDownloader urlDownloader = null) { progress = progress ?? (_ => { }); urlDownloader = urlDownloader ?? new FileDownloader(); var packagesDirectory = Path.Combine(rootAppDirectory, "packages"); int current = 0; int toIncrement = (int)(100.0 / releasesToDownload.Count()); if (Utility.IsHttpUrl(updateUrlOrPath)) { // From Internet await releasesToDownload.ForEachAsync(async x => { var targetFile = Path.Combine(packagesDirectory, x.Filename); await downloadRelease(updateUrlOrPath, x, urlDownloader, targetFile); lock (progress) progress(current += toIncrement); }); } else { // From Disk await releasesToDownload.ForEachAsync(x => { var targetFile = Path.Combine(packagesDirectory, x.Filename); File.Copy( Path.Combine(updateUrlOrPath, x.Filename), targetFile, true); lock (progress) progress(current += toIncrement); }); } }
public static ReleasePackage GetPreviousRelease(IEnumerable <ReleaseEntry> releaseEntries, IReleasePackage package, string targetDir, string prevReleasePath = null, IFullLogger log = null) { { if (releaseEntries == null || !releaseEntries.Any()) { return(null); } var first = releaseEntries .Where(x => x.IsDelta == false).Where(x => x.IsDelta == false) .Where(x => x.Version < package.ToSemanticVersion()).Where(x => x.Version < package.ToSemanticVersion()) .OrderByDescending(x => x.Version).OrderByDescending(x => x.Version) .Select(x => new ReleasePackage(Path.Combine(targetDir, x.Filename), true)) .FirstOrDefault(); if (first != null) { var prevReleaseFilePath = Path.Combine(targetDir, first.SuggestedReleaseFileName); if (!File.Exists(prevReleaseFilePath) && !string.IsNullOrEmpty(prevReleasePath)) { IFileDownloader downloader; if (Utility.IsHttpUrl(prevReleasePath)) { downloader = new FileDownloader(); } else if (Utility.IsFtpUrl(prevReleasePath)) { downloader = new FtpFileDownloader(); } else { return(null); } try { downloader.DownloadFile(Path.Combine(prevReleasePath, first.SuggestedReleaseFileName), prevReleaseFilePath, null); } catch (Exception) { return(null); } } if (File.Exists(prevReleaseFilePath)) { return(new ReleasePackage(prevReleaseFilePath, true)); } } return(null); } }
public async Task DownloadReleases(string updateUrlOrPath, IEnumerable <ReleaseEntry> releasesToDownload, Action <int> progress = null, IFileDownloader urlDownloader = null) { progress = progress ?? (_ => { }); if (urlDownloader == null && Utility.IsHttpUrl(updateUrlOrPath)) { urlDownloader = new FileDownloader(); } else if (urlDownloader == null && Utility.IsFtpUrl(updateUrlOrPath)) { urlDownloader = new FtpFileDownloader(); } var packagesDirectory = Path.Combine(rootAppDirectory, "packages"); double current = 0; double toIncrement = 100.0 / releasesToDownload.Count(); if (Utility.IsHttpUrl(updateUrlOrPath) || Utility.IsFtpUrl(updateUrlOrPath)) { // From Internet await releasesToDownload.ForEachAsync(async x => { var targetFile = Path.Combine(packagesDirectory, x.Filename); double component = 0; await downloadRelease(updateUrlOrPath, x, urlDownloader, targetFile, p => { lock (progress) { current -= component; component = toIncrement / 100.0 * p; progress((int)Math.Round(current += component)); } }); checksumPackage(x); }); } else { // From Disk await releasesToDownload.ForEachAsync(x => { var targetFile = Path.Combine(packagesDirectory, x.Filename); File.Copy( Path.Combine(updateUrlOrPath, x.Filename), targetFile, true); lock (progress) progress((int)Math.Round(current += toIncrement)); checksumPackage(x); }); } }
public static ReleaseEntry ParseReleaseEntry(string entry) { entry = commentRegex.Replace(entry, ""); if (string.IsNullOrWhiteSpace(entry)) { return(null); } Match match1 = entryRegex.Match(entry); if (!match1.Success) { throw new Exception("Invalid release entry: " + entry); } Match local1 = match1; if (local1.Groups.Count != 4) { throw new Exception("Invalid release entry: " + entry); } Match local2 = local1; string urlOrPath = local2.Groups[2].Value; string baseUrl = null; string query = null; if (Utility.IsHttpUrl(urlOrPath)) { Uri uri = new Uri(urlOrPath); string localPath = uri.LocalPath; string leftPart = uri.GetLeftPart(UriPartial.Authority); if (string.IsNullOrEmpty(localPath) || string.IsNullOrEmpty(leftPart)) { throw new Exception("Invalid URL"); } int length = localPath.LastIndexOf("/") + 1; baseUrl = leftPart + localPath.Substring(0, length); urlOrPath = localPath.Substring(length); if (!string.IsNullOrEmpty(uri.Query)) { query = uri.Query; } } if (urlOrPath.IndexOfAny(Path.GetInvalidFileNameChars()) > -1) { throw new Exception("Filename can either be an absolute HTTP[s] URL, *or* a file name"); } Match local3 = local2; return(new ReleaseEntry(local3.Groups[1].Value, urlOrPath, long.Parse(local3.Groups[3].Value), filenameIsDeltaFile(urlOrPath), baseUrl, query)); }
public async Task <UpdateInfo> CheckForUpdate( string localReleaseFile, string updateUrlOrPath, bool ignoreDeltaUpdates = false, Action <int> progress = null, IFileDownloader urlDownloader = null) { progress = progress ?? (_ => { }); var localReleases = Enumerable.Empty <ReleaseEntry>(); bool shouldInitialize = false; try { localReleases = Utility.LoadLocalReleases(localReleaseFile); } catch (Exception ex) { // Something has gone pear-shaped, let's start from scratch this.Log().WarnException("Failed to load local releases, starting from scratch", ex); shouldInitialize = true; } if (shouldInitialize) { await initializeClientAppDirectory(); } string releaseFile; var latestLocalRelease = localReleases.Count() > 0 ? localReleases.MaxBy(x => x.Version).First() : default(ReleaseEntry); // Fetch the remote RELEASES file, whether it's a local dir or an // HTTP URL if (Utility.IsHttpUrl(updateUrlOrPath)) { if (updateUrlOrPath.EndsWith("/")) { updateUrlOrPath = updateUrlOrPath.Substring(0, updateUrlOrPath.Length - 1); } this.Log().Info("Downloading RELEASES file from {0}", updateUrlOrPath); int retries = 3; retry: try { var url = String.Format("{0}/{1}", updateUrlOrPath, "RELEASES"); if (latestLocalRelease != null) { url = String.Format("{0}/RELEASES?id={1}&localVersion={2}&arch={3}", updateUrlOrPath, Uri.EscapeUriString(latestLocalRelease.PackageName), Uri.EscapeUriString(latestLocalRelease.Version.ToString()), Environment.Is64BitOperatingSystem ? "amd64" : "x86"); } var data = await urlDownloader.DownloadUrl(url); releaseFile = Encoding.UTF8.GetString(data); } catch (WebException ex) { this.Log().InfoException("Download resulted in WebException (returning blank release list)", ex); if (retries <= 0) { throw; } retries--; goto retry; } progress(33); } else { this.Log().Info("Reading RELEASES file from {0}", updateUrlOrPath); if (!Directory.Exists(updateUrlOrPath)) { var message = String.Format( "The directory {0} does not exist, something is probably broken with your application", updateUrlOrPath); throw new Exception(message); } var fi = new FileInfo(Path.Combine(updateUrlOrPath, "RELEASES")); if (!fi.Exists) { var message = String.Format( "The file {0} does not exist, something is probably broken with your application", fi.FullName); this.Log().Warn(message); var packages = (new DirectoryInfo(updateUrlOrPath)).GetFiles("*.nupkg"); if (packages.Length == 0) { throw new Exception(message); } // NB: Create a new RELEASES file since we've got a directory of packages ReleaseEntry.WriteReleaseFile( packages.Select(x => ReleaseEntry.GenerateFromFile(x.FullName)), fi.FullName); } releaseFile = File.ReadAllText(fi.FullName, Encoding.UTF8); progress(33); } var ret = default(UpdateInfo); var remoteReleases = ReleaseEntry.ParseReleaseFile(releaseFile); progress(66); if (!remoteReleases.Any()) { throw new Exception("Remote release File is empty or corrupted"); } ret = determineUpdateInfo(localReleases, remoteReleases, ignoreDeltaUpdates); progress(100); return(ret); }
public async Task <UpdateInfo> CheckForUpdate( string localReleaseFile, string updateUrlOrPath, bool ignoreDeltaUpdates = false, Action <int> progress = null, IFileDownloader urlDownloader = null, bool startOverIfNone = false) { progress = progress ?? (_ => { }); var localReleases = Enumerable.Empty <ReleaseEntry>(); bool shouldInitialize = false; try { localReleases = Utility.LoadLocalReleases(localReleaseFile); } catch (Exception ex) { // Something has gone pear-shaped, let's start from scratch this.Log().WarnException("Failed to load local releases, starting from scratch", ex); shouldInitialize = true; } restart: if (shouldInitialize) { await initializeClientAppDirectory(); } string releaseFile; var latestLocalRelease = localReleases.Count() > 0 ? localReleases.MaxBy(x => x.Version).First() : default(ReleaseEntry); // Fetch the remote RELEASES file, whether it's a local dir or an // HTTP URL if (Utility.IsHttpUrl(updateUrlOrPath)) { if (updateUrlOrPath.EndsWith("/")) { updateUrlOrPath = updateUrlOrPath.Substring(0, updateUrlOrPath.Length - 1); } this.Log().Info("Downloading RELEASES file from {0}", updateUrlOrPath); int retries = 3; retry: try { var uri = Utility.AppendPathToUri(new Uri(updateUrlOrPath), "RELEASES"); if (latestLocalRelease != null) { uri = Utility.AddQueryParamsToUri(uri, new Dictionary <string, string> { { "id", latestLocalRelease.PackageName }, { "localVersion", latestLocalRelease.Version.ToString() }, { "arch", Environment.Is64BitOperatingSystem ? "amd64" : "x86" } }); } var data = await urlDownloader.DownloadUrl(uri.ToString()); releaseFile = Encoding.UTF8.GetString(data); } catch (WebException ex) { this.Log().InfoException("Download resulted in WebException (returning blank release list)", ex); if (retries <= 0) { throw; } retries--; goto retry; } progress(33); } else { this.Log().Info("Reading RELEASES file from {0}", updateUrlOrPath); if (!Directory.Exists(updateUrlOrPath)) { var message = String.Format( "The directory {0} does not exist, something is probably broken with your application", updateUrlOrPath); throw new Exception(message); } var fi = new FileInfo(Path.Combine(updateUrlOrPath, "RELEASES")); if (!fi.Exists) { var message = String.Format( "The file {0} does not exist, something is probably broken with your application", fi.FullName); this.Log().Warn(message); var packages = (new DirectoryInfo(updateUrlOrPath)).GetFiles("*.nupkg"); if (packages.Length == 0) { throw new Exception(message); } // NB: Create a new RELEASES file since we've got a directory of packages ReleaseEntry.WriteReleaseFile( packages.Select(x => ReleaseEntry.GenerateFromFile(x.FullName)), fi.FullName); } releaseFile = File.ReadAllText(fi.FullName, Encoding.UTF8); progress(33); } var ret = default(UpdateInfo); var remoteReleases = ReleaseEntry.ParseReleaseFile(releaseFile); progress(66); if (!remoteReleases.Any()) { throw new Exception("Remote release File is empty or corrupted"); } ret = determineUpdateInfo(localReleases, remoteReleases, ignoreDeltaUpdates); progress(100); if (startOverIfNone && !ret.ReleasesToApply.Any()) { // User has apparently re-run the installer for the version already installed. // Assume the intent is to repair a broken installation. // These rather awkward steps cause it to erase the installed-version directory // and re-create it, much like a first-time install (though it won't run the app // with the special arguments for first-time). shouldInitialize = true; localReleases = Enumerable.Empty <ReleaseEntry>(); goto restart; } return(ret); }
public async Task DownloadReleases(string updateUrlOrPath, IEnumerable <ReleaseEntry> releasesToDownload1, Action <int> progress = null, IFileDownloader urlDownloader = null) { progress = progress ?? (_ => { }); urlDownloader = urlDownloader ?? new FileDownloader(); var packagesDirectory = Path.Combine(rootAppDirectory, "packages"); var releasesToDownload = new List <ReleaseEntry>(releasesToDownload1); double current = 0; double toIncrement = 100.0 / releasesToDownload.Count(); if (Utility.IsHttpUrl(updateUrlOrPath)) { // From Internet Exception lastException = null; for (int attempts = 0; ; attempts++) { // Filter out ones we downloaded successfully (perhaps on an earlier run of the program). // We don't want to waste time and bandwidth downloading anything we already have. releasesToDownload = releasesToDownload.Where(x => !isPackageOk(x)).ToList(); if (releasesToDownload.Count == 0 || attempts >= 4) // we got them all (or exceeded max attempt limit) { break; } try { await releasesToDownload.ForEachAsync(async x => { var targetFile = Path.Combine(packagesDirectory, x.Filename); double component = 0; await downloadRelease(updateUrlOrPath, x, urlDownloader, targetFile, p => { lock (progress) { current -= component; component = toIncrement / 100.0 * p; progress((int)Math.Round(current += component)); } }); // With a lot of small updates, and since notifications are only sent every half second // for each one, we can easily miss half or more of the progress on each download. // To make sure we eventually get to 100% of the whole process, we need to update // progress (and especially the total in current) to indicate that this one is complete. lock (progress) { current -= component; component = toIncrement; progress((int)Math.Round(current += component)); } }); } catch (WebException ex) { lastException = ex; } } // If we failed to get all the files, throw the last exception we got; it may provide some clue what went wrong. if (releasesToDownload.Count > 0) { throw lastException ?? new ApplicationException("Download somehow failed to get a full set of valid deltas though no exception was thrown"); } } else { // From Disk await releasesToDownload.ForEachAsync(x => { var targetFile = Path.Combine(packagesDirectory, x.Filename); File.Copy( Path.Combine(updateUrlOrPath, x.Filename), targetFile, true); lock (progress) progress((int)Math.Round(current += toIncrement)); }); } }
public async Task <UpdateInfo> CheckForUpdate( string localReleaseFile, string updateUrlOrPath, bool ignoreDeltaUpdates = false, Action <int> progress = null, IFileDownloader urlDownloader = null) { progress = progress ?? (_ => { }); var localReleases = Enumerable.Empty <ReleaseEntry>(); bool shouldInitialize = false; try { localReleases = LoadLocalReleases(localReleaseFile); } catch (Exception ex) { // Something has gone pear-shaped, let's start from scratch this.Log().WarnException("Failed to load local releases, starting from scratch", ex); shouldInitialize = true; } if (shouldInitialize) { await initializeClientAppDirectory(); } string releaseFile; // Fetch the remote RELEASES file, whether it's a local dir or an // HTTP URL if (Utility.IsHttpUrl(updateUrlOrPath)) { this.Log().Info("Downloading RELEASES file from {0}", updateUrlOrPath); try { var data = await urlDownloader.DownloadUrl(String.Format("{0}/{1}", updateUrlOrPath, "RELEASES")); releaseFile = Encoding.UTF8.GetString(data); } catch (WebException ex) { this.Log().InfoException("Download resulted in WebException (returning blank release list)", ex); releaseFile = String.Empty; } progress(33); } else { this.Log().Info("Reading RELEASES file from {0}", updateUrlOrPath); if (!Directory.Exists(updateUrlOrPath)) { var message = String.Format( "The directory {0} does not exist, something is probably broken with your application", updateUrlOrPath); throw new Exception(message); } var fi = new FileInfo(Path.Combine(updateUrlOrPath, "RELEASES")); if (!fi.Exists) { var message = String.Format( "The file {0} does not exist, something is probably broken with your application", fi.FullName); this.Log().Warn(message); var packages = (new DirectoryInfo(updateUrlOrPath)).GetFiles("*.nupkg"); if (packages.Length == 0) { throw new Exception(message); } // NB: Create a new RELEASES file since we've got a directory of packages ReleaseEntry.WriteReleaseFile( packages.Select(x => ReleaseEntry.GenerateFromFile(x.FullName)), fi.FullName); } releaseFile = File.ReadAllText(fi.FullName, Encoding.UTF8); progress(33); } var ret = default(UpdateInfo); var remoteReleases = ReleaseEntry.ParseReleaseFile(releaseFile); progress(66); if (remoteReleases.Any()) { ret = determineUpdateInfo(localReleases, remoteReleases, ignoreDeltaUpdates); } progress(100); return(ret); }