/// <summary> /// Imports implementation archives into the <see cref="IStore"/>. /// </summary> private void ImportArchives() { foreach (string path in Directory.GetFiles(_contentDir)) { Debug.Assert(path != null); var digest = new ManifestDigest(); digest.ParseID(Path.GetFileNameWithoutExtension(path)); if (digest.Best != null && !Store.Contains(digest)) { try { Store.AddArchives(new[] { new ArchiveFileInfo { Path = path, MimeType = Archive.GuessMimeType(path) } }, digest, Handler); } #region Error handling catch (ImplementationAlreadyInStoreException) {} #endregion } } }
public void ParseIDNoOverwrite() { var digest = new ManifestDigest("sha1=test"); digest.TryParse("sha1=test2"); digest.Sha1.Should().Be("test", because: "Once a digest value has been set, ID values should not overwrite it"); }
public void MultipleArchives() { using var tempFile1 = new TemporaryFile("0install-unit-tests"); using var tempFile2 = new TemporaryFile("0install-unit-tests"); var digest = new ManifestDigest(sha256New: "abc"); string path1 = tempFile1; string path2 = tempFile2; StoreMock.Setup(x => x.AddArchives(new[] { new ArchiveFileInfo(path1, "mime1") { Extract = "extract1" }, new ArchiveFileInfo(path2, "mime2") { Extract = "extract2" } }, digest, Handler)).Returns(""); RunAndAssert(null, ExitCode.OK, "sha256new_" + digest.Sha256New, path1, "extract1", "mime1", path2, "extract2", "mime2"); }
/// <summary> /// Checks whether an implementation directory matches the expected digest. /// Throws <see cref="DigestMismatchException"/> if it does not match. /// </summary> /// <param name="path">The path of the directory ot check.</param> /// <param name="manifestDigest">The expected digest.</param> /// <param name="handler">A callback object used when the the user is to be informed about progress.</param> /// <exception cref="OperationCanceledException">The user canceled the task.</exception> /// <exception cref="NotSupportedException"><paramref name="manifestDigest"/> does not list any supported digests.</exception> /// <exception cref="IOException">The directory could not be processed.</exception> /// <exception cref="UnauthorizedAccessException">Read access to the directory is not permitted.</exception> /// <exception cref="DigestMismatchException">The directory does not match the expected digest</exception> public static void Verify(string path, ManifestDigest manifestDigest, ITaskHandler handler) { #region Sanity checks if (string.IsNullOrEmpty(path)) { throw new ArgumentNullException(nameof(path)); } if (handler == null) { throw new ArgumentNullException(nameof(handler)); } #endregion string expectedDigest = manifestDigest.Best ?? throw new NotSupportedException(Resources.NoKnownDigestMethod); var format = ManifestFormat.FromPrefix(expectedDigest); var builder = new ManifestBuilder(format); handler.RunTask(new ReadDirectory(path, builder)); if (Verify(builder.Manifest, expectedDigest) == null) { string manifestFilePath = Path.Combine(path, Manifest.ManifestFile); var expectedManifest = File.Exists(manifestFilePath) ? Manifest.Load(manifestFilePath, format) : null; throw new DigestMismatchException( expectedDigest, actualDigest: builder.Manifest.CalculateDigest(), expectedManifest, actualManifest: builder.Manifest); } }
public void TestGetImplementation() { var digest1 = new ManifestDigest(sha256: "123"); var implementation1 = new Implementation { ManifestDigest = digest1 }; var feed1 = new Feed { Elements = { implementation1 } }; var digest2 = new ManifestDigest(sha256: "abc"); var implementation2 = new Implementation { ManifestDigest = digest2 }; var feed2 = new Feed { Elements = { implementation2 } }; var feeds = new[] { feed1, feed2 }; Feed feed; Assert.AreEqual(implementation1, feeds.GetImplementation(digest1, out feed)); Assert.AreEqual(feed1, feed); Assert.AreEqual(implementation2, feeds.GetImplementation(digest2, out feed)); Assert.AreEqual(feed2, feed); Assert.IsNull(feeds.GetImplementation(new ManifestDigest(sha256: "invalid"), out feed), "No implementation should have been found"); Assert.IsNull(feed, "No feed should have been found"); }
public void TryParseInvalid() { var digest = new ManifestDigest(); digest.TryParse("invalid"); digest.AvailableDigests.Should().BeEmpty(); }
public void TestGetImplementation() { var digest1 = new ManifestDigest(sha256: "123"); var implementation1 = new Implementation { ManifestDigest = digest1 }; var feed1 = new Feed { Elements = { implementation1 } }; var digest2 = new ManifestDigest(sha256: "abc"); var implementation2 = new Implementation { ManifestDigest = digest2 }; var feed2 = new Feed { Elements = { implementation2 } }; var feeds = new[] { feed1, feed2 }; Feed feed; feeds.GetImplementation(digest1, out feed).Should().Be(implementation1); feed.Should().Be(feed1); feeds.GetImplementation(digest2, out feed).Should().Be(implementation2); feed.Should().Be(feed2); feeds.GetImplementation(new ManifestDigest(sha256: "invalid"), out feed).Should().BeNull(because: "No implementation should have been found"); feed.Should().BeNull(because: "No feed should have been found"); }
/// <summary> /// Executes a specific <see cref="RetrievalMethod"/>. /// </summary> /// <param name="retrievalMethod">The retrieval method to execute.</param> /// <param name="manifestDigest">The digest the result of the retrieval method should produce.</param> /// <exception cref="OperationCanceledException">A download or IO task was canceled from another thread.</exception> /// <exception cref="WebException">A file could not be downloaded from the internet.</exception> /// <exception cref="NotSupportedException">A file format, protocal, etc. is unknown or not supported.</exception> /// <exception cref="IOException">A downloaded file could not be written to the disk or extracted.</exception> /// <exception cref="UnauthorizedAccessException">Write access to <see cref="IStore"/> is not permitted.</exception> /// <exception cref="DigestMismatchException">An <see cref="Implementation"/>'s <see cref="Archive"/>s don't match the associated <see cref="ManifestDigest"/>.</exception> private void Retrieve([NotNull] RetrievalMethod retrievalMethod, ManifestDigest manifestDigest) { var externalRetrievalMethod = retrievalMethod as ExternalRetrievalMethod; if (externalRetrievalMethod != null) { RunNative(externalRetrievalMethod); return; } // Treat single steps as a Recipes for easier handling var recipe = retrievalMethod as Recipe ?? new Recipe { Steps = { (IRecipeStep)retrievalMethod } }; try { Cook(recipe, manifestDigest); } #region Error handling catch (ImplementationAlreadyInStoreException) {} catch (DigestMismatchException ex) { // Wrap exception to add context information throw new DigestMismatchException("Damaged download: " + retrievalMethod, ex); } #endregion }
/// <summary> /// Calculates the <see cref="ManifestDigest"/>. /// </summary> /// <param name="handler">A callback object used when the the user needs to be informed about IO tasks.</param> /// <exception cref="InvalidOperationException"><see cref="ImplementationDirectory"/> is <c>null</c> or empty.</exception> /// <exception cref="OperationCanceledException">The user canceled the task.</exception> /// <exception cref="IOException">There was a problem generating the manifest.</exception> /// <exception cref="UnauthorizedAccessException">Write access to temporary files was not permitted.</exception> public void CalculateDigest(ITaskHandler handler) { #region Sanity checks if (handler == null) { throw new ArgumentNullException(nameof(handler)); } if (string.IsNullOrEmpty(ImplementationDirectory)) { throw new InvalidOperationException("Implementation directory is not set."); } #endregion var newDigest = new ManifestDigest(); // Generate manifest for each available format... foreach (var generator in ManifestFormat.All.Select(format => new ManifestGenerator(ImplementationDirectory, format))) { // ... and add the resulting digest to the return value handler.RunTask(generator); newDigest.ParseID(generator.Manifest.CalculateDigest()); } ManifestDigest = newDigest; }
public override ExitCode Execute() { var manifestDigest = new ManifestDigest(AdditionalArgs[0]); string path = AdditionalArgs[1]; try { if (File.Exists(path)) { // One or more archives (combined/overlay) ImplementationStore.Add(manifestDigest, BuildImplementation); return(ExitCode.OK); } else if (Directory.Exists(path)) { // A single directory if (AdditionalArgs.Count > 2) { throw new OptionException(Resources.TooManyArguments + Environment.NewLine + AdditionalArgs.Skip(2).JoinEscapeArguments(), null); } ImplementationStore.Add(manifestDigest, builder => Handler.RunTask(new ReadDirectory(Path.GetFullPath(path), builder))); return(ExitCode.OK); } else { throw new FileNotFoundException(string.Format(Resources.FileOrDirNotFound, path), path); } } catch (ImplementationAlreadyInStoreException ex) { Log.Warn(ex); return(ExitCode.NoChanges); } }
public override ExitCode Execute() { ManifestDigest digest; string path = Path.GetFullPath(AdditionalArgs[0]).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); string id = Path.GetFileName(path); try { digest = new ManifestDigest(id); } catch (NotSupportedException ex) { Log.Error(ex); return(ExitCode.NotSupported); } var store = (AdditionalArgs.Count == 2) ? new ImplementationStore(AdditionalArgs[1]) : ImplementationStore; try { store.Add(digest, builder => Handler.RunTask(new ReadDirectory(path, builder))); return(ExitCode.OK); } catch (ImplementationAlreadyInStoreException ex) { Log.Warn(ex); return(ExitCode.NoChanges); } }
/// <summary> /// Calculates a <see cref="ManifestDigest"/> for a retrieval method. Sets missing properties in the process. /// </summary> /// <param name="retrievalMethod">The retrieval method.</param> /// <param name="executor">Used to modify properties in an undoable fashion.</param> /// <param name="handler">A callback object used when the the user is to be informed about progress.</param> /// <param name="format">The manifest format. Leave <c>null</c> for default.</param> /// <returns>The generated digest.</returns> /// <exception cref="OperationCanceledException">The user canceled the task.</exception> /// <exception cref="WebException">A file could not be downloaded from the internet.</exception> public static ManifestDigest CalculateDigest(this RetrievalMethod retrievalMethod, ICommandExecutor executor, ITaskHandler handler, ManifestFormat?format = null) { #region Sanity checks if (retrievalMethod == null) { throw new ArgumentNullException(nameof(retrievalMethod)); } if (executor == null) { throw new ArgumentNullException(nameof(executor)); } if (handler == null) { throw new ArgumentNullException(nameof(handler)); } #endregion var builder = new ManifestBuilder(format ?? ManifestFormat.Sha256New); builder.Add(retrievalMethod, executor, handler); var digest = new ManifestDigest(builder.Manifest.CalculateDigest()); if (digest.PartialEquals(ManifestDigest.Empty)) { Log.Warn(Resources.EmptyImplementation); } return(digest); }
public void TryParseEmptyString() { var digest = new ManifestDigest(); digest.TryParse(""); digest.AvailableDigests.Should().BeEmpty(); }
private ImplementationNode?GetImplementationNode(ManifestDigest digest) { Debug.Assert(_feeds != null); try { var found = _feeds.FindImplementation(digest); if (found.HasValue) { return(new OwnedImplementationNode(digest, found.Value.implementation, new FeedNode(found.Value.feed, _feedCache), _implementationStore)); } else { return(new OrphanedImplementationNode(digest, _implementationStore)); } } #region Error handling catch (Exception ex) when(ex is IOException or UnauthorizedAccessException or FormatException) { Log.Error($"Problem processing '{digest}'."); Log.Error(ex); return(null); } #endregion }
public override ExitCode Execute() { var manifestDigest = new ManifestDigest(AdditionalArgs[0]); string path = AdditionalArgs[1]; try { if (File.Exists(path)) { // One or more archives (combined/overlayed) Store.AddArchives(GetArchiveFileInfos(), manifestDigest, Handler); return ExitCode.OK; } else if (Directory.Exists(path)) { // A single directory if (AdditionalArgs.Count > 2) throw new OptionException(Resources.TooManyArguments + Environment.NewLine + AdditionalArgs.Skip(2).JoinEscapeArguments(), null); Store.AddDirectory(Path.GetFullPath(path), manifestDigest, Handler); return ExitCode.OK; } else throw new FileNotFoundException(string.Format(Resources.FileOrDirNotFound, path), path); } catch (ImplementationAlreadyInStoreException ex) { Log.Warn(ex); return ExitCode.NoChanges; } }
public override ExitCode Execute() { var manifestDigest = new ManifestDigest(AdditionalArgs[0]); try { string path = AdditionalArgs[1]; if (Directory.Exists(path)) { if (AdditionalArgs.Count > 2) { throw new OptionException(Resources.TooManyArguments + Environment.NewLine + AdditionalArgs.Skip(2).JoinEscapeArguments(), null); } ImplementationStore.Add(manifestDigest, builder => Handler.RunTask(new ReadDirectory(Path.GetFullPath(path), builder))); return(ExitCode.OK); } ImplementationStore.Add(manifestDigest, BuildImplementation); return(ExitCode.OK); } catch (ImplementationAlreadyInStoreException ex) { Log.Warn(ex.Message, ex); return(ExitCode.NoChanges); } }
/// <summary> /// Gets a GUI element for reporting progress of a <see cref="ITask"/> for a specific implementation. May run multiple in parallel. /// </summary> /// <param name="taskName">The name of the task to be tracked.</param> /// <param name="tag">A digest used to associate the task with a specific implementation.</param> /// <remarks>This method must not be called from a background thread.</remarks> public IProgress <TaskSnapshot> GetProgressControl(string taskName, ManifestDigest tag) { #region Sanity checks if (string.IsNullOrEmpty(taskName)) { throw new ArgumentNullException(nameof(taskName)); } if (InvokeRequired) { throw new InvalidOperationException("Method called from a non UI thread."); } #endregion // Hide other stuff taskControl.Hide(); if (_selectionsShown) { var control = selectionsControl.TaskControls[tag]; control.TaskName = taskName; return(control); } else { return(GetProgressControl(taskName)); } }
/// <inheritdoc /> public void Purge(ITaskHandler handler) { #region Sanity checks if (handler == null) { throw new ArgumentNullException(nameof(handler)); } #endregion var paths = Directory.GetDirectories(Path).Where(path => { var digest = new ManifestDigest(); digest.TryParse(System.IO.Path.GetFileName(path)); return(digest.AvailableDigests.Any()); }).ToList(); if (paths.Count == 0) { return; } if (MissingAdminRights) { throw new NotAdminException(Resources.MustBeAdminToRemove); } handler.RunTask(ForEachTask.Create( name: string.Format(Resources.DeletingDirectory, Path), target: paths, work: path => RemoveInner(path, handler, allowAutoShutdown: true))); RemoveDeleteInfoFile(); }
/// <summary> /// Executes the work-step for a single implementation. /// </summary> public void Work(ManifestDigest manifestDigest) { string digestString = manifestDigest.Best; if (digestString == null) return; string implementationPath = Path.Combine(_storePath, digestString); var manifest = Manifest.Load(Path.Combine(implementationPath, Manifest.ManifestFile), ManifestFormat.FromPrefix(digestString)); string currentDirectory = ""; new AggregateDispatcher<ManifestNode> { (ManifestDirectory x) => { currentDirectory = FileUtils.UnifySlashes(x.FullPath.TrimStart('/')); }, (ManifestFileBase x) => { if (x.Size == 0) return; var key = new DedupKey(x.Size, x.ModifiedTime, manifest.Format, x.Digest); var file = new StoreFile(implementationPath, Path.Combine(currentDirectory, x.FileName)); StoreFile existingFile; if (_fileHashes.TryGetValue(key, out existingFile)) { if (!FileUtils.AreHardlinked(file, existingFile)) { if (JoinWithHardlink(file, existingFile)) SavedBytes += x.Size; } } else _fileHashes.Add(key, file); } }.Dispatch(manifest); }
/// <summary> /// Executes a specific <see cref="RetrievalMethod"/>. /// </summary> /// <param name="retrievalMethod">The retrieval method to execute.</param> /// <param name="manifestDigest">The digest the result of the retrieval method should produce.</param> /// <exception cref="OperationCanceledException">A download or IO task was canceled from another thread.</exception> /// <exception cref="WebException">A file could not be downloaded from the internet.</exception> /// <exception cref="NotSupportedException">A file format, protocol, etc. is unknown or not supported.</exception> /// <exception cref="IOException">A downloaded file could not be written to the disk or extracted.</exception> /// <exception cref="UnauthorizedAccessException">Write access to <see cref="IImplementationStore"/> is not permitted.</exception> /// <exception cref="DigestMismatchException">An <see cref="Implementation"/>'s <see cref="Archive"/>s don't match the associated <see cref="ManifestDigest"/>.</exception> private void Retrieve(RetrievalMethod retrievalMethod, ManifestDigest manifestDigest) { if (retrievalMethod is ExternalRetrievalMethod externalRetrievalMethod) { RunNative(externalRetrievalMethod); return; } // Treat single steps as a Recipes for easier handling var recipe = retrievalMethod as Recipe ?? new Recipe { Steps = { (IRecipeStep)retrievalMethod } }; try { // Enable Recipe steps to call back to Fetcher using (FetchHandle.Register(impl => Fetch(impl, tag: manifestDigest) ?? throw new ImplementationNotFoundException(manifestDigest))) Cook(recipe, manifestDigest); } #region Error handling catch (ImplementationAlreadyInStoreException) {} catch (DigestMismatchException) { Log.Error("Damaged download: " + retrievalMethod); throw; } #endregion }
public override ExitCode Execute() { ManifestDigest manifestDigest; string path = Path.GetFullPath(AdditionalArgs[0]).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); try { manifestDigest = new ManifestDigest(Path.GetFileName(path)); } #region Error handling catch (ArgumentException ex) { // Wrap exception since only certain exception types are allowed throw new IOException(ex.Message); } #endregion var store = (AdditionalArgs.Count == 2) ? new DiskImplementationStore(AdditionalArgs[1]) : ImplementationStore; try { store.AddDirectory(path, manifestDigest, Handler); return(ExitCode.OK); } catch (ImplementationAlreadyInStoreException ex) { Log.Warn(ex); return(ExitCode.NoChanges); } }
protected virtual string VerifyAndAdd(string tempID, ManifestDigest expectedDigest, ITaskHandler handler) { #region Sanity checks if (string.IsNullOrEmpty(tempID)) { throw new ArgumentNullException(nameof(tempID)); } if (handler == null) { throw new ArgumentNullException(nameof(handler)); } #endregion // Determine the digest method to use string?expectedDigestValue = expectedDigest.Best; if (string.IsNullOrEmpty(expectedDigestValue)) { throw new NotSupportedException(Resources.NoKnownDigestMethod); } // Determine the source and target directories string source = Path.Combine(DirectoryPath, tempID); string target = Path.Combine(DirectoryPath, expectedDigestValue); if (_isUnixFS) { FlagUtils.ConvertToFS(source); } // Calculate the actual digest, compare it with the expected one and create a manifest file VerifyDirectory(source, expectedDigest, handler).Save(Path.Combine(source, Manifest.ManifestFile)); lock (_renameLock) // Prevent race-conditions when adding the same digest twice { if (Directory.Exists(target)) { throw new ImplementationAlreadyInStoreException(expectedDigest); } // Move directory to final store destination try { Directory.Move(source, target); } catch (IOException ex) // TODO: Make language independent when(ex.Message.Contains("already exists")) { throw new ImplementationAlreadyInStoreException(expectedDigest); } } // Prevent any further changes to the directory if (_useWriteProtection) { EnableWriteProtection(target); } return(target); }
/// <summary> /// Deserializes an exception. /// </summary> private ImplementationNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context) { #region Sanity checks if (info == null) throw new ArgumentNullException(nameof(info)); #endregion ManifestDigest = (ManifestDigest)info.GetValue("ManifestDigest", typeof(ManifestDigest)); }
public void TryParseSuccess() { var digest = new ManifestDigest(); digest.TryParse("sha1new=test1"); digest.TryParse("sha256new_test2"); digest.AvailableDigests.Should().Equal("sha256new_test2", "sha1new=test1"); }
[Fact] // Ensures all options are parsed and handled correctly. public void TestNormal() { var selections = ExpectSolve(); ExpectFetchUncached(selections, new Implementation { ID = "id1", ManifestDigest = new ManifestDigest(Sha256: "abc"), Version = new("1.0") },
/// <inheritdoc/> public void Add(ManifestDigest manifestDigest, Action <IBuilder> build) { #region Sanity checks if (build == null) { throw new ArgumentNullException(nameof(build)); } #endregion if (manifestDigest.AvailableDigests.Any(digest => Directory.Exists(System.IO.Path.Combine(Path, digest)))) { throw new ImplementationAlreadyInStoreException(manifestDigest); } string expectedDigest = manifestDigest.Best ?? throw new NotSupportedException(Resources.NoKnownDigestMethod); Log.Debug($"Storing implementation {expectedDigest} in {this}"); var format = ManifestFormat.FromPrefix(manifestDigest.Best); // Place files in temp directory until digest is verified string tempDir = GetTempDir(); using var _ = new Disposable(() => DeleteTempDir(tempDir)); var builder = new ManifestBuilder(format); build(new DirectoryBuilder(tempDir, builder)); var manifest = ImplementationStoreUtils.Verify(builder.Manifest, expectedDigest); if (manifest == null) { throw new DigestMismatchException( expectedDigest, actualDigest: builder.Manifest.CalculateDigest(), actualManifest: builder.Manifest); } manifest.Save(System.IO.Path.Combine(tempDir, Manifest.ManifestFile)); string target = System.IO.Path.Combine(Path, expectedDigest); lock (_renameLock) // Prevent race-conditions when adding the same digest twice { if (Directory.Exists(target)) { throw new ImplementationAlreadyInStoreException(manifestDigest); } // Move directory to final destination try { Directory.Move(tempDir, target); } catch (IOException ex) when(ex.Message.Contains("already exists") || Directory.Exists(target)) { throw new ImplementationAlreadyInStoreException(manifestDigest); } } // Prevent any further changes to the directory if (UseWriteProtection) { EnableWriteProtection(target); } }
/// <summary> /// Sets the <see cref="ManifestDigest"/> in the <see cref="ContainerRef"/>. /// </summary> /// <param name="digest">The digest to set.</param> /// <param name="executor">Used to apply properties in an undoable fashion.</param> private void SetDigest(ManifestDigest digest, ICommandExecutor executor) { executor.Execute(new SetValueCommand <ManifestDigest>(() => ContainerRef.ManifestDigest, value => ContainerRef.ManifestDigest = value, digest)); if (string.IsNullOrEmpty(ContainerRef.ID) || ContainerRef.ID.Contains(@"=")) { executor.Execute(new SetValueCommand <string>(() => ContainerRef.ID, value => ContainerRef.ID = value, @"sha1new=" + digest.Sha1New)); } }
public void Fail() { var digest = new ManifestDigest(sha256New: "abc"); StoreMock.Setup(x => x.Verify(digest, Handler)).Throws <DigestMismatchException>(); RunAndAssert(new DigestMismatchException().Message, ExitCode.DigestMismatch, "sha256new_" + digest.Sha256New); }
public void Test() { var digest = new ManifestDigest(sha256New: "abc"); StoreMock.Setup(x => x.Remove(digest, Handler)).Returns(true); RunAndAssert(null, ExitCode.OK, "sha256new_abc"); }
public void Pass() { var digest = new ManifestDigest(sha256New: "abc"); StoreMock.Setup(x => x.Verify(digest, Handler)); RunAndAssert(null, ExitCode.OK, "sha256new_" + digest.Sha256New); }
public void TestVerifyPass() { var digest = new ManifestDigest(sha256New: "abc"); StoreMock.Setup(x => x.Verify(digest, Resolve <ICommandHandler>())); RunAndAssert(null, ExitCode.OK, "verify", "sha256new_" + digest.Sha256New); }
public void Test() { var digest = new ManifestDigest(sha256New: "abc"); StoreMock.Setup(x => x.GetPath(digest)).Returns("path"); RunAndAssert("path", ExitCode.OK, "sha256new_abc"); }
public void TestGetTree() { var digest1 = new ManifestDigest(sha256New: "a"); var digest2 = new ManifestDigest(sha256New: "b"); _storeMock.Setup(x => x.GetPath(digest1)).Returns("fake/path"); _storeMock.Setup(x => x.GetPath(digest2)).Returns(() => null); var tree = _selectionsManager.GetTree(new Selections { InterfaceUri = new FeedUri("http://root/"), Implementations = { new ImplementationSelection { InterfaceUri = new FeedUri("http://root/"), ID = "a", ManifestDigest = digest1, Version = new ImplementationVersion("1.0"), Dependencies = { new Dependency { InterfaceUri = new FeedUri("http://dependency/") }, new Dependency { InterfaceUri = new FeedUri("http://missing/") } } }, new ImplementationSelection { InterfaceUri = new FeedUri("http://dependency/"), ID = "b", ManifestDigest = digest2, Version = new ImplementationVersion("2.0"), Dependencies = { new Dependency{ InterfaceUri = new FeedUri("http://root/") } } // Exercise cycle detection } } }); var node1 = new SelectionsTreeNode(new FeedUri("http://root/"), new ImplementationVersion("1.0"), "fake/path", parent: null); node1.ToString().Should().Be("- URI: http://root/\n Version: 1.0\n Path: fake/path"); var node2 = new SelectionsTreeNode(new FeedUri("http://dependency/"), new ImplementationVersion("2.0"), path: null, parent: node1); node2.ToString().Should().Be($" - URI: http://dependency/\n Version: 2.0\n {Resources.NotCached}"); var node3 = new SelectionsTreeNode(new FeedUri("http://missing/"), version: null, path: null, parent: node1); node3.ToString().Should().Be($" - URI: http://missing/\n {Resources.NoSelectedVersion}"); tree.Should().Equal(node1, node2, node3); }
public void ShouldAllowToAddFolder() { using (var packageDir = new TemporaryDirectory("0install-unit-tests")) { var digest = new ManifestDigest(ManifestTest.CreateDotFile(packageDir, ManifestFormat.Sha256, _handler)); _store.AddDirectory(packageDir, digest, _handler); Assert.IsTrue(_store.Contains(digest), "After adding, Store must contain the added package"); CollectionAssert.AreEqual(new[] {digest}, _store.ListAll(), "After adding, Store must show the added package in the complete list"); } }
public void TestParseID() { Assert.AreEqual("test", new ManifestDigest("sha1=test").Sha1); Assert.AreEqual("test", new ManifestDigest("sha1new=test").Sha1New); Assert.AreEqual("test", new ManifestDigest("sha256=test").Sha256); Assert.AreEqual("test", new ManifestDigest("sha256new_test").Sha256New); // Once a digest value has been set, ID values shall not be able to overwrite it var digest = new ManifestDigest("sha1=test"); digest.ParseID("sha1=test2"); Assert.AreEqual("test", digest.Sha1); }
public void TestParseID() { new ManifestDigest("sha1=test").Sha1.Should().Be("test"); new ManifestDigest("sha1new=test").Sha1New.Should().Be("test"); new ManifestDigest("sha256=test").Sha256.Should().Be("test"); new ManifestDigest("sha256new_test").Sha256New.Should().Be("test"); // Once a digest value has been set, ID values shall not be able to overwrite it var digest = new ManifestDigest("sha1=test"); digest.ParseID("sha1=test2"); digest.Sha1.Should().Be("test"); }
/// <summary> /// Creates a new owned implementation node. /// </summary> /// <param name="digest">The digest identifying the implementation.</param> /// <param name="implementation">Information about the implementation from a <see cref="Feed"/> file.</param> /// <param name="parent">The node of the feed owning the implementation.</param> /// <param name="store">The <see cref="IStore"/> the implementation is located in.</param> /// <exception cref="FormatException">The manifest file is not valid.</exception> /// <exception cref="IOException">The manifest file could not be read.</exception> /// <exception cref="UnauthorizedAccessException">Read access to the file is not permitted.</exception> public OwnedImplementationNode(ManifestDigest digest, [NotNull] Implementation implementation, [NotNull] FeedNode parent, [NotNull] IStore store) : base(digest, store) { #region Sanity checks if (implementation == null) throw new ArgumentNullException("implementation"); if (parent == null) throw new ArgumentNullException("parent"); if (store == null) throw new ArgumentNullException("store"); #endregion _parent = parent; _implementation = implementation; }
/// <summary> /// Creates a new implementation node. /// </summary> /// <param name="digest">The digest identifying the implementation.</param> /// <param name="store">The <see cref="IStore"/> the implementation is located in.</param> /// <exception cref="FormatException">The manifest file is not valid.</exception> /// <exception cref="IOException">The manifest file could not be read.</exception> /// <exception cref="UnauthorizedAccessException">Read access to the file is not permitted.</exception> protected ImplementationNode(ManifestDigest digest, [NotNull] IStore store) : base(store) { #region Sanity checks if (store == null) throw new ArgumentNullException("store"); #endregion _digest = digest; // Determine the total size of an implementation via its manifest file string path = store.GetPath(digest); if (path == null) return; string manifestPath = System.IO.Path.Combine(path, Manifest.ManifestFile); Size = Manifest.Load(manifestPath, ManifestFormat.FromPrefix(digest.AvailableDigests.FirstOrDefault())).TotalSize; }
/// <inheritdoc/> public string AddArchives(IEnumerable<ArchiveFileInfo> archiveInfos, ManifestDigest manifestDigest, ITaskHandler handler) { try { string result = GetServiceProxy().AddArchives(archiveInfos, manifestDigest, handler); Log.Info("Sent implementation to Store Service: " + manifestDigest.AvailableDigests.First()); return result; } #region Error handling catch (RemotingException ex) { // Wrap exception since only certain exception types are allowed throw new IOException(ex.Message, ex); } #endregion }
public static string GetPathSafe([NotNull] this IStore store, ManifestDigest manifestDigest) { #region Sanity checks if (store == null) throw new ArgumentNullException("store"); #endregion try { return store.GetPath(manifestDigest); } #region Error handling catch (UnauthorizedAccessException) { return null; } #endregion }
public void TestPartialEqual() { var digest1 = new ManifestDigest(sha1: "test1"); var digest2 = new ManifestDigest(sha1: "test1", sha1New: "test2"); digest1.PartialEquals(digest2).Should().BeTrue(); digest1 = new ManifestDigest(sha1: "test1"); digest2 = new ManifestDigest(sha1: "test2"); digest1.PartialEquals(digest2).Should().BeFalse(); digest1 = new ManifestDigest(sha1: "test1"); digest2 = new ManifestDigest(sha1New: "test2"); digest1.PartialEquals(digest2).Should().BeFalse(); digest1 = new ManifestDigest(sha1New: "test1"); digest2 = new ManifestDigest(sha256: "test2"); digest1.PartialEquals(digest2).Should().BeFalse(); }
public void TestPartialEqual() { var digest1 = new ManifestDigest(sha1: "test1"); var digest2 = new ManifestDigest(sha1: "test1", sha1New: "test2"); Assert.IsTrue(digest1.PartialEquals(digest2)); digest1 = new ManifestDigest(sha1: "test1"); digest2 = new ManifestDigest(sha1: "test2"); Assert.IsFalse(digest1.PartialEquals(digest2)); digest1 = new ManifestDigest(sha1: "test1"); digest2 = new ManifestDigest(sha1New: "test2"); Assert.IsFalse(digest1.PartialEquals(digest2)); digest1 = new ManifestDigest(sha1New: "test1"); digest2 = new ManifestDigest(sha256: "test2"); Assert.IsFalse(digest1.PartialEquals(digest2)); }
public static Implementation GetImplementation([NotNull] this IEnumerable<Feed> feeds, ManifestDigest digest, out Feed feed) { #region Sanity checks if (feeds == null) throw new ArgumentNullException(nameof(feeds)); #endregion foreach (var curFeed in feeds) { var impl = curFeed.Elements.OfType<Implementation>().FirstOrDefault(implementation => implementation.ManifestDigest.PartialEquals(digest)); if (impl != null) { feed = curFeed; return impl; } } feed = null; return null; }
public void TestGetImplementation() { var digest1 = new ManifestDigest(sha256: "123"); var implementation1 = new Implementation {ManifestDigest = digest1}; var feed1 = new Feed {Elements = {implementation1}}; var digest2 = new ManifestDigest(sha256: "abc"); var implementation2 = new Implementation {ManifestDigest = digest2}; var feed2 = new Feed {Elements = {implementation2}}; var feeds = new[] {feed1, feed2}; Feed feed; Assert.AreEqual(implementation1, feeds.GetImplementation(digest1, out feed)); Assert.AreEqual(feed1, feed); Assert.AreEqual(implementation2, feeds.GetImplementation(digest2, out feed)); Assert.AreEqual(feed2, feed); Assert.IsNull(feeds.GetImplementation(new ManifestDigest(sha256: "invalid"), out feed), "No implementation should have been found"); Assert.IsNull(feed, "No feed should have been found"); }
public void TestGetImplementation() { var digest1 = new ManifestDigest(sha256: "123"); var implementation1 = new Implementation {ManifestDigest = digest1}; var feed1 = new Feed {Elements = {implementation1}}; var digest2 = new ManifestDigest(sha256: "abc"); var implementation2 = new Implementation {ManifestDigest = digest2}; var feed2 = new Feed {Elements = {implementation2}}; var feeds = new[] {feed1, feed2}; Feed feed; feeds.GetImplementation(digest1, out feed).Should().Be(implementation1); feed.Should().Be(feed1); feeds.GetImplementation(digest2, out feed).Should().Be(implementation2); feed.Should().Be(feed2); feeds.GetImplementation(new ManifestDigest(sha256: "invalid"), out feed).Should().BeNull(because: "No implementation should have been found"); feed.Should().BeNull(because: "No feed should have been found"); }
/// <summary> /// Does nothing. Should be handled by an <see cref="DirectoryStore"/> directly instead of using the service. /// </summary> public bool Remove(ManifestDigest manifestDigest, ITaskHandler handler) { return false; }
/// <inheritdoc/> public string AddDirectory(string path, ManifestDigest manifestDigest, ITaskHandler handler) { try { string result = GetServiceProxy().AddDirectory(path, manifestDigest, handler); Log.Info("Sent implementation to Store Service: " + manifestDigest.Best); return result; } #region Error handling catch (RemotingException ex) { // Wrap exception since only certain exception types are allowed throw new IOException(ex.Message, ex); } catch (SerializationException ex) { // Wrap exception since only certain exception types are allowed throw new IOException(ex.Message, ex); } #endregion }
/// <summary> /// Always returns <c>null</c>. Use a non-IPC <see cref="IStore"/> for this method instead. /// </summary> /// <remarks>Using the store service for this is unnecessary since it only requires read access to the file system.</remarks> public string GetPath(ManifestDigest manifestDigest) { return null; }
/// <summary> /// Executes a <see cref="Recipe"/>. /// </summary> /// <param name="recipe">The recipe to execute.</param> /// <param name="manifestDigest">The digest the result of the recipe should produce.</param> /// <exception cref="OperationCanceledException">A download or IO task was canceled from another thread.</exception> /// <exception cref="WebException">A file could not be downloaded from the internet.</exception> /// <exception cref="NotSupportedException">A file format, protocal, etc. is unknown or not supported.</exception> /// <exception cref="IOException">A downloaded file could not be written to the disk or extracted.</exception> /// <exception cref="ImplementationAlreadyInStoreException">There is already an <see cref="Store.Model.Implementation"/> with the specified <paramref name="manifestDigest"/> in the store.</exception> /// <exception cref="UnauthorizedAccessException">Write access to <see cref="IStore"/> is not permitted.</exception> /// <exception cref="DigestMismatchException">An <see cref="Store.Model.Implementation"/>'s <see cref="Archive"/>s don't match the associated <see cref="ManifestDigest"/>.</exception> private void Cook([NotNull] Recipe recipe, ManifestDigest manifestDigest) { Handler.CancellationToken.ThrowIfCancellationRequested(); // Fail fast on unsupported archive type foreach (var archive in recipe.Steps.OfType<Archive>()) Extractor.VerifySupport(archive.MimeType); var downloadedFiles = new List<TemporaryFile>(); try { foreach (var downloadStep in recipe.Steps.OfType<DownloadRetrievalMethod>()) downloadedFiles.Add(Download(downloadStep, tag: manifestDigest)); // More efficient special-case handling for Archive-only cases if (recipe.Steps.All(step => step is Archive)) ApplyArchives(recipe.Steps.Cast<Archive>().ToList(), downloadedFiles, manifestDigest); else ApplyRecipe(recipe, downloadedFiles, manifestDigest); } finally { foreach (var downloadedFile in downloadedFiles) downloadedFile.Dispose(); } }
/// <summary> /// Executes a specific <see cref="RetrievalMethod"/>. /// </summary> /// <param name="retrievalMethod">The retrieval method to execute.</param> /// <param name="manifestDigest">The digest the result of the retrieval method should produce.</param> /// <exception cref="OperationCanceledException">A download or IO task was canceled from another thread.</exception> /// <exception cref="WebException">A file could not be downloaded from the internet.</exception> /// <exception cref="NotSupportedException">A file format, protocal, etc. is unknown or not supported.</exception> /// <exception cref="IOException">A downloaded file could not be written to the disk or extracted.</exception> /// <exception cref="UnauthorizedAccessException">Write access to <see cref="IStore"/> is not permitted.</exception> /// <exception cref="DigestMismatchException">An <see cref="Store.Model.Implementation"/>'s <see cref="Archive"/>s don't match the associated <see cref="ManifestDigest"/>.</exception> private void Retrieve([NotNull] RetrievalMethod retrievalMethod, ManifestDigest manifestDigest) { var externalRetrievalMethod = retrievalMethod as ExternalRetrievalMethod; if (externalRetrievalMethod != null) { RunNative(externalRetrievalMethod); return; } // Treat single steps as a Recipes for easier handling var recipe = retrievalMethod as Recipe ?? new Recipe {Steps = {(IRecipeStep)retrievalMethod}}; try { Cook(recipe, manifestDigest); } #region Error handling catch (ImplementationAlreadyInStoreException) {} catch (DigestMismatchException ex) { // Wrap exception to add context information throw new DigestMismatchException("Damaged download: " + retrievalMethod, ex); } #endregion }
public void StressTest() { using (var packageDir = new TemporaryDirectory("0install-unit-tests")) { new PackageBuilder().AddFolder("subdir") .AddFile("file", "AAA", new DateTime(2000, 1, 1)) .WritePackageInto(packageDir); var digest = new ManifestDigest(ManifestTest.CreateDotFile(packageDir, ManifestFormat.Sha256, _handler)); Exception exception = null; var threads = new Thread[100]; for (int i = 0; i < threads.Length; i++) { threads[i] = new Thread(() => { try { // ReSharper disable once AccessToDisposedClosure _store.AddDirectory(packageDir, digest, _handler); _store.Remove(digest, _handler); } catch (ImplementationAlreadyInStoreException) {} catch (ImplementationNotFoundException) {} catch (Exception ex) { exception = ex; } }); threads[i].Start(); } foreach (var thread in threads) thread.Join(); if (exception != null) Assert.Fail(exception.ToString()); Assert.IsFalse(_store.Contains(digest)); } }
/// <summary> /// Creates a new implementation not found exception. /// </summary> /// <param name="manifestDigest">The <see cref="ManifestDigest"/> of the <see cref="Store.Model.Implementation"/> to be found.</param> public ImplementationNotFoundException(ManifestDigest manifestDigest) : base(string.Format(Resources.ImplementationNotFound, manifestDigest)) { ManifestDigest = manifestDigest; }
/// <summary> /// Creates a new GUI element for tracking a <see cref="ITask"/> for a specific implementation and returns a handle to it. May run multiple in parallel. /// </summary> /// <param name="taskName">The name of the task to be tracked.</param> /// <param name="tag">A digest used to associate the task with a specific implementation.</param> /// <remarks>This method must not be called from a background thread.</remarks> public IProgress<TaskSnapshot> SetupProgress(string taskName, ManifestDigest tag) { #region Sanity checks if (string.IsNullOrEmpty(taskName)) throw new ArgumentNullException("taskName"); if (InvokeRequired) throw new InvalidOperationException("Method called from a non UI thread."); #endregion // Hide other stuff trackingControl.Hide(); if (_selectionsShown) { var control = selectionsControl.TrackingControls[tag]; control.TaskName = taskName; return new Progress<TaskSnapshot>(control.Report); } else return SetupProgress(taskName); }
public override void installPackageWithVerification (System.Uri packageURI, IPackageInstallObserver observer, int flags, string installerPackageName, System.Uri verificationURI, ManifestDigest manifestDigest) { throw new NotImplementedException (); }
public void ShouldRecreateMissingStoreDir() { Directory.Delete(_tempDir, recursive: true); using (var packageDir = new TemporaryDirectory("0install-unit-tests")) { var digest = new ManifestDigest(ManifestTest.CreateDotFile(packageDir, ManifestFormat.Sha256, _handler)); _store.AddDirectory(packageDir, digest, _handler); Assert.IsTrue(_store.Contains(digest), "After adding, Store must contain the added package"); CollectionAssert.AreEqual(new[] {digest}, _store.ListAll(), "After adding, Store must show the added package in the complete list"); Assert.IsTrue(Directory.Exists(_tempDir), "Store directory should have been recreated"); } }
/// <summary> /// Does nothing. Should be handled by an <see cref="DirectoryStore"/> directly instead of using the service. /// </summary> public void Verify(ManifestDigest manifestDigest, ITaskHandler handler) {}
/// <summary> /// Always returns <c>false</c>. Use a non-IPC <see cref="IStore"/> for this method instead. /// </summary> /// <remarks>Using the store service for this is unnecessary since it only requires read access to the file system.</remarks> public bool Contains(ManifestDigest manifestDigest) { return false; }
private TaskControl CreateTrackingControl(ManifestDigest manifestDigest) { var trackingControl = new TaskControl {Dock = DockStyle.Fill}; trackingControl.CreateGraphics(); // Ensure control initialization even in tray icon mode int i = _selections.Implementations.FindIndex(x => x.ManifestDigest.PartialEquals(manifestDigest)); tableLayout.Controls.Add(trackingControl, 2, i); return trackingControl; }
/// <summary> /// Creates a new implementation already in store exception. /// </summary> /// <param name="manifestDigest">The digest of the <see cref="Store.Model.Implementation"/> that was supposed to be added.</param> public ImplementationAlreadyInStoreException(ManifestDigest manifestDigest) : base(string.Format(Resources.ImplementationAlreadyInStore, manifestDigest)) { ManifestDigest = manifestDigest; }
public void TestAuditPass() { using (var packageDir = new TemporaryDirectory("0install-unit-tests")) { new PackageBuilder().AddFolder("subdir") .AddFile("file", "AAA", new DateTime(2000, 1, 1)) .WritePackageInto(packageDir); var digest = new ManifestDigest(ManifestTest.CreateDotFile(packageDir, ManifestFormat.Sha1New, _handler)); _store.AddDirectory(packageDir, digest, _handler); _store.Verify(digest, _handler); Assert.IsNull(_handler.LastQuestion); } }