/// <summary> /// /// </summary> /// <param name="job"></param> /// <param name="build"></param> /// <param name="artifactRegex"></param> /// <returns></returns> protected Artifact FindDefaultArtifactForBuild( Job job, Build build, string artifactRegex) { var artifacts = this.GetArtifactListFromJob(job); if (!artifacts.Any()) { throw new Exception($"Requested job '{job.jobId}' from version '{build.version}' has no artifacts."); } // Try to find a suitable artifact... Artifact artifact = null; // Search by regex. if (!string.IsNullOrWhiteSpace(artifactRegex)) { foreach (var arf in artifacts) { string arfname = (string)arf.fileName; if (Regex.IsMatch(arfname, artifactRegex)) { if (artifact != null) { throw new Exception($"Ambiguous artifact match for regex: '{artifactRegex}'."); } artifact = arf; } } } if (artifact != null) { return(artifact); } // If there is only one artifact use that one. if (artifacts.Count() == 1) { return(artifacts.First()); } // Use an artifact whose name defaults to the project's name artifact = (from p in artifacts where string.Equals(p.name, build.project.name, StringComparison.InvariantCultureIgnoreCase) select p).FirstOrDefault(); if (artifact != null) { return(artifact); } // Now use the file name artifact = (from p in artifacts where string.Equals(p.fileName, build.project.name + ".zip", StringComparison.InvariantCultureIgnoreCase) select p).FirstOrDefault(); if (artifact != null) { return(artifact); } // Our best bet is to find the artifact with the biggest size... artifact = artifacts.OrderByDescending((i) => i.size).Where((i) => string.Equals(i.type, "zip", StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault(); if (artifact == null) { throw new Exception($"Could not find suitable artifact. Regex: '{artifactRegex}'."); } return(artifact); }
/// <summary> /// Downloads (And extracts) single artifacts from jobs. /// </summary> /// <param name="applicationId"></param> /// <param name="build"></param> /// <param name="artifactRegex"></param> /// <param name="destinationPath"></param> /// <param name="logger"></param> public void DownloadSingleArtifactFromBuild( string applicationId, Build build, string artifactRegex, string destinationPath, ILoggerInterface logger) { UtilsSystem.EnsureDirectoryExists(destinationPath, true); // Use the first job in the build... var job = build.jobs.First(); var artifact = this.FindDefaultArtifactForBuild(job, build, artifactRegex); var filename = artifact.fileName; var extension = Path.GetExtension(filename); string downloadTemporaryDir = UtilsSystem.EnsureDirectoryExists(UtilsSystem.CombinePaths(this.TempDir, "_appveyor", "dld", applicationId), true); int artifactRetentionNum = 5; int artifactAgeHoursForStale = 24; // Do not touch the latest artifactRetentionNum artifacts or artifacts that are not older than artifactAgeHoursForStale hours var staleFiles = Directory.EnumerateFiles(downloadTemporaryDir) .Select((i) => new FileInfo(i)) .Where((i) => i.Extension.Equals(".zip", StringComparison.CurrentCultureIgnoreCase)) .OrderByDescending((i) => i.CreationTimeUtc) .Skip(artifactRetentionNum) .Where((i) => (DateTime.UtcNow - i.LastWriteTime).TotalHours > artifactAgeHoursForStale) .ToList(); foreach (var f in staleFiles) { // Make this fail proof, it's just a cleanup. try { this.Logger.LogInfo(true, "Removing stale artifact cache file {0}", f.FullName); f.Delete(); } catch { // ignored } } // Use a short hash as the temporary file name, because long paths can have issues... var tmpFile = UtilsSystem.CombinePaths(downloadTemporaryDir, UtilsEncryption.GetShortHash(JsonConvert.SerializeObject(build) + filename) + extension); if (Path.GetExtension(tmpFile)?.ToLower() != ".zip") { throw new NotImplementedException("AppVeyor artifacts should only be Zip Files."); } if (!File.Exists(tmpFile)) { // Use an intermediate .tmp file just in case the files does not finish to download, // if it exists, clear it. string tmpFileDownload = tmpFile + ".tmp"; if (File.Exists(tmpFileDownload)) { UtilsSystem.RetryWhile(() => File.Delete(tmpFileDownload), (e) => true, 4000, this.Logger); } var url = $"/api/buildjobs/{job.jobId}/artifacts/{filename}"; logger.LogInfo(true, "Downloading artifact from: '{0}' to '{1}'", url, tmpFileDownload); this.ExecuteApiCallToFile(url, tmpFileDownload); // Rename to the final cached artifact file logger.LogInfo(true, "Download succesful, moving to '{0}'", tmpFile); UtilsSystem.RetryWhile(() => File.Move(tmpFileDownload, tmpFile), (e) => true, 4000, this.Logger); } else { logger.LogInfo(true, "Skipping artifact download, already in local cache: {0}", tmpFile); } logger.LogInfo(true, "Unzipping {1} file to '{0}'...", destinationPath, UtilsSystem.BytesToString(new FileInfo(tmpFile).Length)); ZipFile.ExtractToDirectory(tmpFile, destinationPath); logger.LogInfo(true, "Unzipping finished."); }