/// <summary> /// Helper function to download package from given url, and place package (only 'content' folder from package) to given folder /// </summary> /// <param name="identity">Package identity</param> /// <param name="destinationFolder">Folder where we copy the package content (content folder only) to</param> /// <param name="pathToLocalCopyOfNupkg">File path where we copy the nudpk to</param> /// <returns></returns> public static async Task DownloadPackageToFolder(this SourceRepository srcRepo, PackageIdentity identity, string destinationFolder, string pathToLocalCopyOfNupkg) { var downloadResource = await srcRepo.GetResourceAndValidateAsync <DownloadResource>(); using (Stream packageStream = await srcRepo.GetPackageStream(identity)) { WriteStreamToFile(packageStream, pathToLocalCopyOfNupkg); // set position back to the head of stream packageStream.Position = 0; using (ZipFile zipFile = ZipFile.Read(packageStream)) { // we only care about stuff under "content" folder int substringStartIndex = @"content/".Length; IEnumerable <ZipEntry> contentEntries = zipFile.Entries.Where(e => e.FileName.StartsWith(@"content/", StringComparison.InvariantCultureIgnoreCase)); foreach (var entry in contentEntries) { string fullPath = Path.Combine(destinationFolder, entry.FileName.Substring(substringStartIndex)); FileSystemHelpers.EnsureDirectory(Path.GetDirectoryName(fullPath)); using (Stream writeStream = FileSystemHelpers.OpenWrite(fullPath)) { // let the thread go with itself, so that once file finsihed writing, doesn`t need to request thread context from main thread await entry.OpenReader().CopyToAsync(writeStream).ConfigureAwait(false); } } } } }
public static async Task UpdateLocalPackage(this SourceRepository srcRepo, SourceRepository localRepo, PackageIdentity identity, string destinationFolder, string pathToLocalCopyOfNupkg, ITracer tracer) { tracer.Trace("Performing incremental package update for {0}", identity.Id); using (Stream newPackageStream = await srcRepo.GetPackageStream(identity)) { // update file var localPackage = await localRepo.GetLatestPackageByIdFromSrcRepo(identity.Id); if (localPackage == null) { throw new FileNotFoundException(string.Format(CultureInfo.InvariantCulture, "Package {0} not found from local repo.", identity.Id)); } using (Stream oldPackageStream = await localRepo.GetPackageStream(localPackage.Identity)) using (ZipFile oldPackageZip = ZipFile.Read(oldPackageStream)) using (ZipFile newPackageZip = ZipFile.Read(newPackageStream)) { // we only care about stuff under "content" folder IEnumerable <ZipEntry> oldContentEntries = oldPackageZip.Entries.Where(e => e.FileName.StartsWith(@"content/", StringComparison.InvariantCultureIgnoreCase)); IEnumerable <ZipEntry> newContentEntries = newPackageZip.Entries.Where(e => e.FileName.StartsWith(@"content/", StringComparison.InvariantCultureIgnoreCase)); List <ZipEntry> filesNeedToUpdate = new List <ZipEntry>(); Dictionary <string, ZipEntry> indexedOldFiles = new Dictionary <string, ZipEntry>(); foreach (var item in oldContentEntries) { indexedOldFiles.Add(item.FileName.ToLowerInvariant(), item); } foreach (var newEntry in newContentEntries) { var fileName = newEntry.FileName.ToLowerInvariant(); if (indexedOldFiles.ContainsKey(fileName)) { // file name existed, only update if file has been touched ZipEntry oldEntry = indexedOldFiles[fileName]; if (oldEntry.LastModified != newEntry.LastModified) { filesNeedToUpdate.Add(newEntry); } // remove from old index files buffer, the rest will be files that need to be deleted indexedOldFiles.Remove(fileName); } else { // new files filesNeedToUpdate.Add(newEntry); } } int substringStartIndex = @"content/".Length; foreach (var entry in filesNeedToUpdate) { string fullPath = Path.Combine(destinationFolder, entry.FileName.Substring(substringStartIndex)); if (entry.IsDirectory) { using (tracer.Step("Ensure directory: {0}", fullPath)) { FileSystemHelpers.EnsureDirectory(fullPath.Replace('/', '\\')); } continue; } using (tracer.Step("Adding/Updating file: {0}", fullPath)) { FileSystemHelpers.EnsureDirectory(Path.GetDirectoryName(fullPath)); using (Stream writeStream = FileSystemHelpers.OpenWrite(fullPath)) { // reset length of file stream writeStream.SetLength(0); // let the thread go with itself, so that once file finishes writing, doesn't need to request thread context from main thread await entry.OpenReader().CopyToAsync(writeStream).ConfigureAwait(false); } } } foreach (var entry in indexedOldFiles.Values) { string fullPath = Path.Combine(destinationFolder, entry.FileName.Substring(substringStartIndex)); if (entry.IsDirectory) { // in case the two zip file was created from different tool. some tool will include folder as seperate entry, some don`t. // to be sure that foder is meant to be deleted, double check there is no files under it var entryNameInLower = entry.FileName.ToLower(); if (!string.Equals(destinationFolder, fullPath, StringComparison.OrdinalIgnoreCase) && newContentEntries.FirstOrDefault(e => e.FileName.ToLowerInvariant().StartsWith(entryNameInLower)) == null) { using (tracer.Step("Deleting directory: {0}", fullPath)) { FileSystemHelpers.DeleteDirectorySafe(fullPath); } } continue; } using (tracer.Step("Deleting file: {0}", fullPath)) { FileSystemHelpers.DeleteFileSafe(fullPath); } } } // update nupkg newPackageStream.Position = 0; using (tracer.Step("Updating nupkg file.")) { WriteStreamToFile(newPackageStream, pathToLocalCopyOfNupkg); if (!identity.Version.Equals(localPackage.Identity.Version)) { using (tracer.Step("New package has difference version {0} from old package {1}. Remove old nupkg file.", identity.Version, localPackage.Identity.Version)) { // if version is difference, nupkg file name will be difference. will need to clean up the old one. var oldNupkg = pathToLocalCopyOfNupkg.Replace( string.Format(CultureInfo.InvariantCulture, "{0}.{1}.nupkg", identity.Id, identity.Version.ToNormalizedString()), string.Format(CultureInfo.InvariantCulture, "{0}.{1}.nupkg", localPackage.Identity.Id, localPackage.Identity.Version.ToNormalizedString())); FileSystemHelpers.DeleteFileSafe(oldNupkg); } } } } }