public void TestApplyRecipeCopyFrom() { using var existingImplDir = new TemporaryDirectory("0install-unit-tests"); using var archiveFile = new TemporaryFile("0install-unit-tests"); new TestRoot { new TestDirectory("source") { new TestFile("file"), new TestFile("executable") { IsExecutable = true }, new TestSymlink("symlink", "target") } }.Build(existingImplDir); typeof(ArchiveExtractorTest).CopyEmbeddedToFile("testArchive.zip", archiveFile); var downloadedFiles = new[] { archiveFile }; var recipe = new Recipe { Steps = { new CopyFromStep { Source = "source", Destination = "dest", Implementation = new Implementation { ManifestDigest = new ManifestDigest(sha1New: "123") } }, new CopyFromStep { Source = "source/file", Destination = "dest/symlink", // Overwrite existing symlink with regular file Implementation = new Implementation { ManifestDigest = new ManifestDigest(sha1New: "123") } } } }; using (FetchHandle.Register(_ => existingImplDir)) { using var recipeDir = recipe.Apply(downloadedFiles, new SilentTaskHandler()); new TestRoot { new TestDirectory("dest") { new TestFile("file"), new TestFile("executable") { IsExecutable = true }, new TestFile("symlink") } }.Verify(recipeDir); } FileUtils.DisableWriteProtection(existingImplDir); Directory.Delete(existingImplDir, recursive: true); }
/// <summary> /// Applies a <see cref="CopyFromStep"/> to a <see cref="TemporaryDirectory"/>. <see cref="FetchHandle.Register"/> must be called first on the same thread. /// </summary> /// <param name="step">The <see cref="Archive"/> to apply.</param> /// <param name="workingDir">The <see cref="TemporaryDirectory"/> to apply the changes to.</param> /// <param name="handler">A callback object used when the the user needs to be informed about progress.</param> /// <param name="tag">A tag used to associate composite task with a specific operation; can be null.</param> /// <exception cref="IOException">A path specified in <paramref name="step"/> is illegal.</exception> /// <exception cref="ArgumentException"><see cref="CopyFromStep.Implementation"/> is <c>null</c>. Please call <see cref="Feed.ResolveInternalReferences"/> first.</exception> /// <exception cref="InvalidOperationException"><see cref="FetchHandle.Register"/> was not called first.</exception> public static void Apply(this CopyFromStep step, TemporaryDirectory workingDir, ITaskHandler handler, object?tag = null) { #region Sanity checks if (step == null) { throw new ArgumentNullException(nameof(step)); } if (workingDir == null) { throw new ArgumentNullException(nameof(workingDir)); } if (handler == null) { throw new ArgumentNullException(nameof(handler)); } #endregion string source = FileUtils.UnifySlashes(step.Source ?? ""); string destination = FileUtils.UnifySlashes(step.Destination ?? ""); if (FileUtils.IsBreakoutPath(source)) { throw new IOException(string.Format(Resources.RecipeInvalidPath, source)); } if (FileUtils.IsBreakoutPath(destination)) { throw new IOException(string.Format(Resources.RecipeInvalidPath, destination)); } if (step.Implementation == null) { throw new ArgumentException(string.Format(Resources.UnableToResolveRecipeReference, step, "")); } string sourcePath = Path.Combine(FetchHandle.Use(step.Implementation), source); if (Directory.Exists(sourcePath)) { handler.RunTask(new CloneDirectory(sourcePath, workingDir) { TargetSuffix = destination, UseHardlinks = true, Tag = tag }); } else if (File.Exists(sourcePath)) { if (string.IsNullOrEmpty(destination)) { throw new IOException(string.Format(Resources.RecipeCopyFromDestinationMissing, step)); } handler.RunTask(new CloneFile(sourcePath, workingDir) { TargetSuffix = Path.GetDirectoryName(destination), TargetFileName = Path.GetFileName(destination), UseHardlinks = true, Tag = tag }); } else { throw new IOException(string.Format(Resources.RecipeCopyFromSourceMissing, step)); } }