/// <summary> /// Applies a <see cref="SingleFile"/> to a <see cref="TemporaryDirectory"/>. /// </summary> /// <param name="step">The <see cref="Archive"/> to apply.</param> /// <param name="localPath">The local path of the file.</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> /// <exception cref="IOException">A path specified in <paramref name="step"/> is illegal.</exception> public static void Apply(this SingleFile step, string localPath, TemporaryDirectory workingDir, ITaskHandler handler) { #region Sanity checks if (step == null) { throw new ArgumentNullException(nameof(step)); } if (string.IsNullOrEmpty(localPath)) { throw new ArgumentNullException(nameof(localPath)); } if (workingDir == null) { throw new ArgumentNullException(nameof(workingDir)); } if (handler == null) { throw new ArgumentNullException(nameof(handler)); } #endregion // Use a copy of the original file because the source file is moved using var tempFile = new TemporaryFile("0install"); // ReSharper disable once AccessToDisposedClosure handler.RunTask(new SimpleTask(Resources.CopyFiles, () => File.Copy(localPath, tempFile, overwrite: true))); step.Apply(tempFile, workingDir); }
/// <inheritdoc/> public string GetIcon(Uri iconUrl, ITaskHandler handler) { #region Sanity checks if (iconUrl == null) { throw new ArgumentNullException("iconUrl"); } if (handler == null) { throw new ArgumentNullException("handler"); } #endregion string path = Path.Combine(DirectoryPath, new FeedUri(iconUrl).Escape()); // Prevent file-exists race conditions lock (_lock) { // Download missing icons if (!File.Exists(path)) { using (var atomic = new AtomicWrite(path)) { handler.RunTask(new DownloadFile(iconUrl, atomic.WritePath)); atomic.Commit(); } } } return(path); }
/// <summary> /// Creates a archive containing the <see cref="InstallationDir"/>. /// </summary> /// <remarks>Sets <see cref="FeedBuilder.RetrievalMethod"/> and calls <see cref="FeedBuilder.CalculateDigest"/>.</remarks> /// <param name="archivePath">The path of the archive file to create.</param> /// <param name="archiveUrl">The URL where the archive will be uploaded.</param> /// <param name="handler">A callback object used when the the user needs to be informed about IO tasks.</param> /// <exception cref="InvalidOperationException"><see cref="Diff"/> was not called or <see cref="FeedBuilder.MainCandidate"/> is not set.</exception> /// <exception cref="OperationCanceledException">The user canceled the task.</exception> /// <exception cref="IOException">There was an error reading the installation files or writing the archive.</exception> /// <exception cref="UnauthorizedAccessException">Access to the file system was not permitted.</exception> public void CollectFiles([NotNull] string archivePath, [NotNull] Uri archiveUrl, [NotNull] ITaskHandler handler) { #region Sanity checks if (string.IsNullOrEmpty(archivePath)) { throw new ArgumentNullException(nameof(archivePath)); } if (archiveUrl == null) { throw new ArgumentNullException(nameof(archiveUrl)); } if (handler == null) { throw new ArgumentNullException(nameof(handler)); } #endregion if (InstallationDir == null) { throw new InvalidOperationException("Diff() must be called first."); } _feedBuilder.ImplementationDirectory = InstallationDir; _feedBuilder.CalculateDigest(handler); var mimeType = Archive.GuessMimeType(archivePath) ?? Archive.MimeTypeZip; using (var generator = ArchiveGenerator.Create(InstallationDir, archivePath, mimeType)) handler.RunTask(generator); _feedBuilder.RetrievalMethod = new Archive { Href = archiveUrl, MimeType = mimeType, Size = new FileInfo(archivePath).Length }; }
/// <summary> /// Downloads the installer from the web to a temporary file. /// </summary> /// <param name="url">The URL of the file to download.</param> /// <param name="handler">A callback object used when the the user is to be informed about progress.</param> /// <exception cref="WebException">A file could not be downloaded from the internet.</exception> /// <exception cref="IOException">A downloaded file could not be written to the disk.</exception> /// <exception cref="UnauthorizedAccessException">An operation failed due to insufficient rights.</exception> /// <remarks>Use either this or <see cref="SetLocal"/>.</remarks> public void Download([NotNull] Uri url, [NotNull] ITaskHandler handler) { _url = url; if (_tempDir != null) { _tempDir.Dispose(); } _tempDir = new TemporaryDirectory("0publish"); try { _localPath = Path.Combine(_tempDir, Path.GetFileName(url.LocalPath)); handler.RunTask(new DownloadFile(url, _localPath)); } #region Error handling catch (Exception) { _tempDir.Dispose(); _tempDir = null; _url = null; _localPath = null; throw; } #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 <see langword="null"/> or empty.</exception> /// <exception cref="OperationCanceledException">The user canceled the task.</exception> /// <exception cref="IOException">There was a problem generating the manifest or detectng the executables.</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("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.Recommended.Select(format => new ManifestGenerator(ImplementationDirectory, format))) { // ... and add the resulting digest to the return value handler.RunTask(generator); newDigest.ParseID(generator.Result.CalculateDigest()); } ManifestDigest = newDigest; }
private void DownloadMissingKey(FeedUri uri, FeedUri mirrorUrl, MissingKeySignature signature) { var keyUri = new Uri(mirrorUrl ?? uri, signature.KeyID + ".gpg"); byte[] keyData; if (keyUri.IsFile) { // Load key file from local file keyData = File.ReadAllBytes(keyUri.LocalPath); } else { // Load key file from server try { using (var keyFile = new TemporaryFile("0install-key")) { _handler.RunTask(new DownloadFile(keyUri, keyFile)); keyData = File.ReadAllBytes(keyFile); } } #region Error handling catch (WebException ex) { // Wrap exception to add context information throw new SignatureException(string.Format(Resources.UnableToLoadKeyFile, uri), ex); } #endregion } Log.Info("Importing OpenPGP public key for " + signature.KeyID); _openPgp.ImportKey(keyData); }
/// <inheritdoc/> public string GetIcon(Uri iconUrl, ITaskHandler handler) { #region Sanity checks if (iconUrl == null) throw new ArgumentNullException(nameof(iconUrl)); if (handler == null) throw new ArgumentNullException(nameof(handler)); #endregion string path = Path.Combine(DirectoryPath, new FeedUri(iconUrl).Escape()); // Prevent file-exists race conditions lock (_lock) { // Download missing icons if (!File.Exists(path)) { using (var atomic = new AtomicWrite(path)) { handler.RunTask(new DownloadFile(iconUrl, atomic.WritePath)); atomic.Commit(); } } } return path; }
/// <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); } }
private static Manifest GenerateManifest(string path, ManifestFormat format, ITaskHandler handler) { var generator = new ManifestGenerator(path, format); handler.RunTask(generator); return(generator.Result); }
/// <inheritdoc/> public virtual long Optimise(ITaskHandler handler) { #region Sanity checks if (handler == null) { throw new ArgumentNullException(nameof(handler)); } #endregion if (!Directory.Exists(DirectoryPath)) { return(0); } if (Kind == StoreKind.ReadOnly && !WindowsUtils.IsAdministrator) { throw new NotAdminException(Resources.MustBeAdminToOptimise); } using (var run = new OptimiseRun(DirectoryPath)) { handler.RunTask(ForEachTask.Create( name: string.Format(Resources.FindingDuplicateFiles, DirectoryPath), target: ListAll(), work: run.Work)); return(run.SavedBytes); } }
public static TemporaryFile Download([NotNull] this DownloadRetrievalMethod retrievalMethod, [NotNull] ITaskHandler handler, [CanBeNull] ICommandExecutor executor = null) { #region Sanity checks if (retrievalMethod == null) { throw new ArgumentNullException("retrievalMethod"); } if (handler == null) { throw new ArgumentNullException("handler"); } #endregion if (executor == null) { executor = new SimpleCommandExecutor(); } if (retrievalMethod.Href == null) { throw new ArgumentException(Resources.HrefMissing, "retrievalMethod"); } new PerTypeDispatcher <DownloadRetrievalMethod>(ignoreMissing: false) { // ReSharper disable AccessToDisposedClosure (Archive archive) => { // Guess MIME types now because the file ending is not known later if (string.IsNullOrEmpty(archive.MimeType)) { string mimeType = Archive.GuessMimeType(archive.Href.OriginalString); executor.Execute(new SetValueCommand <string>(() => archive.MimeType, value => archive.MimeType = value, mimeType)); } }, (SingleFile file) => { // Guess file name based on URL if (string.IsNullOrEmpty(file.Destination)) { string destination = file.Href.OriginalString.GetRightPartAtLastOccurrence('/').StripCharacters(Path.GetInvalidFileNameChars()); executor.Execute(new SetValueCommand <string>(() => file.Destination, value => file.Destination = value, destination)); } } // ReSharper restore AccessToDisposedClosure }.Dispatch(retrievalMethod); // Download the file var href = ModelUtils.GetAbsoluteHref(retrievalMethod.Href, string.IsNullOrEmpty(executor.Path) ? null : new FeedUri(executor.Path)); var downloadedFile = new TemporaryFile("0publish"); handler.RunTask(new DownloadFile(href, downloadedFile)); // Defer task to handler // Set downloaded file size long newSize = new FileInfo(downloadedFile).Length; if (retrievalMethod.Size != newSize) { executor.Execute(new SetValueCommand <long>(() => retrievalMethod.Size, value => retrievalMethod.Size = value, newSize)); } return(downloadedFile); }
private void Download(Uri href, string path) { using var atomic = new AtomicWrite(path); _handler.RunTask(new DownloadFile(href, atomic.WritePath) { BytesMaximum = MaximumIconSize }); atomic.Commit(); }
/// <inheritdoc/> public string AddArchives(IEnumerable <ArchiveFileInfo> archiveInfos, ManifestDigest manifestDigest, ITaskHandler handler) { #region Sanity checks if (archiveInfos == null) { throw new ArgumentNullException(nameof(archiveInfos)); } if (handler == null) { throw new ArgumentNullException(nameof(handler)); } if (manifestDigest.Best == null) { throw new ArgumentException("No known digest method", nameof(manifestDigest)); } #endregion if (Contains(manifestDigest)) { throw new ImplementationAlreadyInStoreException(manifestDigest); } Log.Info("Caching implementation: " + manifestDigest.Best); // Extract to temporary directory inside the cache so it can be validated safely (no manipulation of directory while validating) string tempDir = GetTempDir(); try { // Extract archives "over each other" in order foreach (var archiveInfo in archiveInfos) { try { using (var extractor = ArchiveExtractor.Create(archiveInfo.Path, tempDir, archiveInfo.MimeType, archiveInfo.StartOffset)) { extractor.SubDir = archiveInfo.SubDir; extractor.Destination = archiveInfo.Destination; extractor.Tag = manifestDigest; handler.RunTask(extractor); } } #region Error handling catch (IOException ex) { string source = archiveInfo.OriginalSource?.ToStringRfc() ?? archiveInfo.Path; throw new IOException(string.Format(Resources.FailedToExtractArchive, source), ex); } #endregion } return(VerifyAndAdd(Path.GetFileName(tempDir), manifestDigest, handler)); } finally { DeleteTempDir(tempDir); } }
/// <summary> /// Runs the installer and waits for it to exit. /// </summary> /// <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="IOException">There is a problem access a temporary file.</exception> /// <exception cref="UnauthorizedAccessException">Read or write access to a temporary file is not permitted.</exception> public void RunInstaller(ITaskHandler handler) { if (string.IsNullOrEmpty(_localPath)) { throw new InvalidOperationException(); } var process = ProcessUtils.Start(_localPath); handler.RunTask(new SimpleTask(Resources.WaitingForInstaller, () => process.WaitForExit())); }
/// <inheritdoc/> public string AddDirectory(string path, ManifestDigest manifestDigest, ITaskHandler handler) { #region Sanity checks if (string.IsNullOrEmpty(path)) { throw new ArgumentNullException("path"); } if (handler == null) { throw new ArgumentNullException("handler"); } #endregion if (Contains(manifestDigest)) { throw new ImplementationAlreadyInStoreException(manifestDigest); } Log.Info("Caching implementation: " + manifestDigest.AvailableDigests.First()); // Copy to temporary directory inside the cache so it can be validated safely (no manipulation of directory while validating) string tempDir = GetTempDir(); try { // Copy the source directory inside the cache so it can be validated safely (no manipulation of directory while validating) try { handler.RunTask(new CopyDirectoryPosix(path, tempDir) { Tag = manifestDigest }); } #region Error handling catch (IOException ex) { // Wrap too generic exceptions // TODO: Make language independent if (ex.Message.StartsWith("Access") && ex.Message.EndsWith("is denied.")) { throw new UnauthorizedAccessException(ex.Message, ex); } // Pass other exceptions through throw; } #endregion return(VerifyAndAdd(Path.GetFileName(tempDir), manifestDigest, handler)); } finally { DeleteTempDir(tempDir); } }
/// <summary> /// Deletes this temporary directory from the <see cref="IStore"/> it is located in. /// </summary> /// <param name="handler">A callback object used when the the user needs to be asked questions or informed about IO tasks.</param> /// <exception cref="DirectoryNotFoundException">The directory could be found in the store.</exception> /// <exception cref="IOException">The directory could not be deleted.</exception> /// <exception cref="UnauthorizedAccessException">Write access to the store is not permitted.</exception> public override void Delete(ITaskHandler handler) { #region Sanity checks if (handler == null) throw new ArgumentNullException(nameof(handler)); #endregion handler.RunTask(new SimpleTask(string.Format(Resources.DeletingDirectory, _path), () => { DirectoryStore.DisableWriteProtection(_path); Directory.Delete(_path, recursive: true); })); }
/// <inheritdoc/> public Selections Solve(Requirements requirements) { #region Sanity checks if (requirements == null) { throw new ArgumentNullException("requirements"); } if (requirements.InterfaceUri == null) { throw new ArgumentException(Resources.MissingInterfaceUri, "requirements"); } #endregion Log.Info("Running Python Solver for: " + requirements); // Execute the external solver ISolverControl control; if (WindowsUtils.IsWindows) { control = new SolverControlBundled(_handler); // Use bundled Python on Windows } else { control = new SolverControlNative(_handler); // Use native Python everywhere else } string arguments = GetSolverArguments(requirements); string result = null; _handler.RunTask(new SimpleTask(Resources.ExternalSolverRunning, () => { result = control.ExecuteSolver(arguments); })); // Flush in-memory cache in case external solver updated something on-disk _feedManager.Flush(); // Detect when feeds get out-of-date _feedManager.Stale = result.Contains("<!-- STALE_FEEDS -->"); // Parse StandardOutput data as XML _handler.CancellationToken.ThrowIfCancellationRequested(); try { var selections = XmlStorage.FromXmlString <Selections>(result); selections.Normalize(); return(selections); } #region Error handling catch (InvalidDataException ex) { Log.Warn("Solver result:" + Environment.NewLine + result); throw new SolverException(Resources.ExternalSolverOutputErrror, ex); } #endregion }
/// <inheritdoc/> public string AddDirectory(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)); } if (manifestDigest.Best == null) { throw new ArgumentException("No known digest method", nameof(manifestDigest)); } #endregion if (Contains(manifestDigest)) { throw new ImplementationAlreadyInStoreException(manifestDigest); } Log.Info($"Caching implementation {manifestDigest} in {this}"); // Copy to temporary directory inside the cache so it can be validated safely (no manipulation of directory while validating) string tempDir = GetTempDir(); try { // Copy the source directory inside the cache so it can be validated safely (no manipulation of directory while validating) try { handler.RunTask(new CloneDirectory(path, tempDir) { Tag = manifestDigest }); } #region Error handling catch (IOException ex) // TODO: Make language independent when(ex.Message.StartsWith("Access") && ex.Message.EndsWith("is denied.")) { throw new UnauthorizedAccessException(ex.Message, ex); } #endregion return(VerifyAndAdd(Path.GetFileName(tempDir), manifestDigest, handler)); } finally { DeleteTempDir(tempDir); } }
/// <summary> /// Copies files or directories from another implementation fetched by an external 0install process. /// </summary> /// <param name="builder">The builder.</param> /// <param name="metadata">The path of the source and destination file or directory.</param> /// <param name="handler">A callback object used when the the user needs to be informed about IO tasks.</param> /// <exception cref="UnauthorizedAccessException">Access to a resource was denied.</exception> /// <exception cref="IOException">An IO operation failed.</exception> public static void CopyFrom(this IBuilder builder, CopyFromStep metadata, ITaskHandler handler) { if (metadata.Implementation == null) { throw new ArgumentException($"Must call {nameof(IRecipeStep.Normalize)}() first.", nameof(metadata)); } handler.RunTask(new SimpleTask(string.Format(Resources.FetchingExternal, metadata.ID), () => ZeroInstallClient.Detect.FetchAsync(metadata.Implementation).Wait())); string path = ImplementationStores.Default().GetPath(metadata.Implementation); builder.CopyFrom(metadata, path, handler); }
/// <summary> /// Removes all applications from the <see cref="AppList"/> and undoes any desktop environment integration. /// </summary> /// <param name="handler">A callback object used when the the user is to be informed about the progress of long-running operations such as downloads.</param> /// <param name="machineWide">Apply the operation machine-wide instead of just for the current user.</param> public static void RemoveAllApps(ITaskHandler handler, bool machineWide) { #region Sanity checks if (handler == null) throw new ArgumentNullException(nameof(handler)); #endregion using (var integrationManager = new IntegrationManager(handler, machineWide)) { handler.RunTask(ForEachTask.Create(Resources.RemovingApplications, integrationManager.AppList.Entries.ToList(), integrationManager.RemoveApp)); // Purge sync status, otherwise next sync would remove everything from server as well instead of restoring from there File.Delete(AppList.GetDefaultPath(machineWide) + SyncIntegrationManager.AppListLastSyncSuffix); } }
/// <summary> /// Deletes this implementation from the <see cref="IImplementationStore"/> it is located in. /// </summary> /// <param name="handler">A callback object used when the the user needs to be asked questions or informed about IO tasks.</param> /// <exception cref="KeyNotFoundException">No matching implementation could be found in the <see cref="IImplementationStore"/>.</exception> /// <exception cref="IOException">The implementation could not be deleted.</exception> /// <exception cref="UnauthorizedAccessException">Write access to the store is not permitted.</exception> public override void Delete(ITaskHandler handler) { try { handler.RunTask(new SimpleTask( string.Format(Resources.DeletingImplementation, _digest), () => ImplementationStore.Remove(_digest, handler))); } #region Error handling catch (ImplementationNotFoundException ex) { throw new KeyNotFoundException(ex.Message, ex); } #endregion }
private void Download(FeedUri feedUri) { SetLastCheckAttempt(feedUri); try { var download = new DownloadMemory(feedUri); _handler.RunTask(download); ImportFeed(download.GetData(), feedUri); } catch (WebException ex) when(!feedUri.IsLoopback) { if (_handler.Verbosity == Verbosity.Batch) { Log.Info(string.Format(Resources.FeedDownloadError, feedUri) + " " + Resources.TryingFeedMirror); } else { Log.Warn(string.Format(Resources.FeedDownloadError, feedUri) + " " + Resources.TryingFeedMirror); } try { var download = new DownloadMemory(GetMirrorUrl(feedUri)) { NoCache = Refresh }; _handler.RunTask(download); ImportFeed(download.GetData(), feedUri); } catch (WebException) { // Report the original problem instead of mirror errors throw ex.PreserveStack(); } } }
/// <summary> /// Deletes this temporary directory from the <see cref="IStore"/> it is located in. /// </summary> /// <param name="handler">A callback object used when the the user needs to be asked questions or informed about IO tasks.</param> /// <exception cref="DirectoryNotFoundException">The directory could be found in the store.</exception> /// <exception cref="IOException">The directory could not be deleted.</exception> /// <exception cref="UnauthorizedAccessException">Write access to the store is not permitted.</exception> public override void Delete(ITaskHandler handler) { #region Sanity checks if (handler == null) { throw new ArgumentNullException(nameof(handler)); } #endregion handler.RunTask(new SimpleTask(string.Format(Resources.DeletingDirectory, _path), () => { DirectoryStore.DisableWriteProtection(_path); Directory.Delete(_path, recursive: true); })); }
/// <summary> /// Downloads and imports a remote key file. /// </summary> /// <exception cref="WebException">The key file could not be downloaded from the internet.</exception> /// <exception cref="SignatureException">The downloaded key file is damaged.</exception> /// <exception cref="IOException">A problem occurs while writing trust configuration.</exception> /// <exception cref="UnauthorizedAccessException">Write access to the trust configuration is not permitted.</exception> private void DownloadKey([NotNull] Uri keyUri) { var download = new DownloadMemory(keyUri); _handler.RunTask(download); try { _openPgp.ImportKey(download.GetData()); } #region Error handling catch (InvalidDataException ex) { // Wrap exception since only certain exception types are allowed throw new SignatureException(ex.Message, ex); } #endregion }
/// <inheritdoc/> protected override string VerifyAndAdd(string tempID, ManifestDigest expectedDigest, ITaskHandler handler) { #region Sanity checks if (handler == null) { throw new ArgumentNullException("handler"); } #endregion var callingIdentity = WindowsIdentity.GetCurrent(); Debug.Assert(callingIdentity != null); using (_serviceIdentity.Impersonate()) // Use system rights instead of calling user { try { var tempDirectory = new DirectoryInfo(Path.Combine(DirectoryPath, tempID)); try { handler.RunTask(new SimpleTask(Resources.SettingFilePermissions, tempDirectory.ResetAcl) { Tag = expectedDigest }); } catch (IndexOutOfRangeException) { // Workaround for .NET 2.0 bug } string result = base.VerifyAndAdd(tempID, expectedDigest, handler); _eventLog.WriteEntry(string.Format(Resources.SuccessfullyAddedImplementation, callingIdentity.Name, expectedDigest.AvailableDigests.FirstOrDefault(), DirectoryPath)); return(result); } #region Error handling catch (OperationCanceledException) { throw; } catch (Exception) { _eventLog.WriteEntry(string.Format(Resources.FailedToAddImplementation, callingIdentity.Name, expectedDigest.AvailableDigests.FirstOrDefault(), DirectoryPath), EventLogEntryType.Warning); throw; } #endregion } }
/// <inheritdoc/> public Selections Solve(Requirements requirements) { #region Sanity checks if (requirements == null) { throw new ArgumentNullException(nameof(requirements)); } if (requirements.InterfaceUri == null) { throw new ArgumentException(Resources.MissingInterfaceUri, nameof(requirements)); } #endregion Selections selections = null; _handler.RunTask(new SimpleTask(Resources.ExternalSolverRunning, () => { using (var control = new JsonControl(GetStartInfo()) { { "confirm", args => DoConfirm((string)args[0]) }, { "confirm-keys", args => DoConfirmKeys(new FeedUri((string)args[0]), args[1].ReparseAsJson <Dictionary <string, string[][]> >()) }, { "update-key-info", args => null } }) { control.Invoke(args => { if ((string)args[0] == "ok") { _feedManager.Stale = args[1].ReparseAsJson(new { stale = false }).stale; selections = XmlStorage.FromXmlString <Selections>((string)args[2]); } else { throw new SolverException(((string)args[1]).Replace("\n", Environment.NewLine)); } }, "select", GetEffectiveRequirements(requirements), _feedManager.Refresh); while (selections == null) { control.HandleStderr(); control.HandleNextChunk(); } control.HandleStderr(); } })); return(selections); }
/// <summary> /// Removes all applications from the <see cref="AppList"/> and undoes any desktop environment integration. /// </summary> /// <param name="handler">A callback object used when the the user is to be informed about the progress of long-running operations such as downloads.</param> /// <param name="machineWide">Apply the operation machine-wide instead of just for the current user.</param> public static void RemoveAllApps(ITaskHandler handler, bool machineWide) { #region Sanity checks if (handler == null) { throw new ArgumentNullException(nameof(handler)); } #endregion using (var integrationManager = new IntegrationManager(handler, machineWide)) { handler.RunTask(ForEachTask.Create(Resources.RemovingApplications, integrationManager.AppList.Entries.ToList(), integrationManager.RemoveApp)); // Purge sync status, otherwise next sync would remove everything from server as well instead of restoring from there File.Delete(AppList.GetDefaultPath(machineWide) + SyncIntegrationManager.AppListLastSyncSuffix); } }
/// <summary> /// Applies a <see cref="Archive"/> to a <see cref="TemporaryDirectory"/>. /// </summary> /// <param name="step">The <see cref="Archive"/> to apply.</param> /// <param name="localPath">The local path of the archive.</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">The <see cref="ITaskHandler"/> tag used by <paramref name="handler"/>; can be <c>null</c>.</param> /// <exception cref="IOException">A path specified in <paramref name="step"/> is illegal.</exception> public static void Apply([NotNull] this Archive step, [NotNull] string localPath, [NotNull] TemporaryDirectory workingDir, [NotNull] ITaskHandler handler, [CanBeNull] object tag = null) { #region Sanity checks if (step == null) { throw new ArgumentNullException(nameof(step)); } if (string.IsNullOrEmpty(localPath)) { throw new ArgumentNullException(nameof(localPath)); } if (workingDir == null) { throw new ArgumentNullException(nameof(workingDir)); } if (handler == null) { throw new ArgumentNullException(nameof(handler)); } #endregion #region Path validation if (!string.IsNullOrEmpty(step.Destination)) { string destination = FileUtils.UnifySlashes(step.Destination); if (FileUtils.IsBreakoutPath(destination)) { throw new IOException(string.Format(Resources.RecipeInvalidPath, destination)); } } #endregion if (string.IsNullOrEmpty(step.MimeType)) { throw new IOException(Resources.UnknownArchiveType); } using (var extractor = ArchiveExtractor.Create(localPath, workingDir, step.MimeType)) { extractor.SubDir = step.Extract; extractor.Destination = FileUtils.UnifySlashes(step.Destination); extractor.Tag = tag; handler.RunTask(extractor); } }
/// <inheritdoc/> protected override string VerifyAndAdd(string tempID, ManifestDigest expectedDigest, ITaskHandler handler) { #region Sanity checks if (handler == null) { throw new ArgumentNullException(nameof(handler)); } #endregion var callingIdentity = WindowsIdentity.GetCurrent(); Debug.Assert(callingIdentity != null); using (_serviceIdentity.Impersonate()) // Use system rights instead of calling user { try { var tempDirectory = new DirectoryInfo(System.IO.Path.Combine(Path, tempID)); try { handler.RunTask(new SimpleTask($"{Resources.SettingFilePermissions} ({expectedDigest.Best})", tempDirectory.ResetAcl) { Tag = expectedDigest.Best }); } catch (IndexOutOfRangeException) { // Workaround for .NET 2.0 bug } string result = base.VerifyAndAdd(tempID, expectedDigest, handler); Log.Info(string.Format(Resources.SuccessfullyAddedImplementation, callingIdentity.Name, expectedDigest.AvailableDigests.FirstOrDefault(), Path)); return(result); } catch (OperationCanceledException) { throw; } catch (Exception ex) { Log.Warn(string.Format(Resources.FailedToAddImplementation, callingIdentity.Name, expectedDigest.AvailableDigests.FirstOrDefault(), Path) + Environment.NewLine + ex.Message); throw; } } }
/// <summary> /// Downloads an <see cref="Implementation"/> to the <see cref="IImplementationStore"/>. /// </summary> /// <param name="implementation">The implementation to download.</param> /// <param name="tag">A <see cref="ITask.Tag"/> used to group progress bars.</param> protected virtual void Fetch(Implementation implementation, string tag) { // Use mutex to detect in-progress download of same implementation in other processes string mutexName = $"0install-fetcher-{implementation.ManifestDigest.Best}"; using var mutex = new Mutex(false, mutexName); try { while (!mutex.WaitOne(100, exitContext: false)) // NOTE: Might be blocked more than once { Handler.RunTask(new WaitTask(Resources.WaitingForDownload, mutex) { Tag = tag }); } } #region Error handling catch (AbandonedMutexException) { Log.Warn($"Mutex '{mutexName}' was abandoned by another instance"); // Abandoned mutexes get acquired despite exception } #endregion try { // Check if another process added the implementation in the meantime if (GetPath(implementation) != null) { return; } if (implementation.RetrievalMethods.Count == 0) { throw new NotSupportedException(string.Format(Resources.NoRetrievalMethod, implementation.ID)); } Retrieve(implementation, tag); } finally { mutex.ReleaseMutex(); } }
public static Manifest VerifyDirectory(string directory, ManifestDigest expectedDigest, ITaskHandler handler) { #region Sanity checks if (string.IsNullOrEmpty(directory)) { throw new ArgumentNullException(nameof(directory)); } if (handler == null) { throw new ArgumentNullException(nameof(handler)); } #endregion string?expectedDigestValue = expectedDigest.Best; if (string.IsNullOrEmpty(expectedDigestValue)) { throw new NotSupportedException(Resources.NoKnownDigestMethod); } var format = ManifestFormat.FromPrefix(expectedDigestValue); var generator = new ManifestGenerator(directory, format) { Tag = expectedDigest }; handler.RunTask(generator); var manifest = generator.Manifest; string digest = manifest.CalculateDigest(); if (digest != expectedDigestValue) { var offsetManifest = TryFindOffset(manifest, expectedDigestValue); if (offsetManifest != null) { return(offsetManifest); } string manifestFilePath = Path.Combine(directory, Manifest.ManifestFile); var expectedManifest = File.Exists(manifestFilePath) ? Manifest.Load(manifestFilePath, format) : null; throw new DigestMismatchException(expectedDigestValue, digest, expectedManifest, manifest); } return(manifest); }
/// <inheritdoc/> public virtual bool Remove(ManifestDigest manifestDigest, ITaskHandler handler) { #region Sanity checks if (handler == null) { throw new ArgumentNullException(nameof(handler)); } #endregion string path = GetPath(manifestDigest); if (path == null) { return(false); } if (Kind == ImplementationStoreKind.ReadOnly && !WindowsUtils.IsAdministrator) { throw new NotAdminException(Resources.MustBeAdminToRemove); } if (path == Locations.InstallBase && WindowsUtils.IsWindows) { Log.Warn(Resources.NoStoreSelfRemove); return(false); } if (WindowsUtils.IsWindowsVista) { if (!UseRestartManager(path, handler)) { return(false); } } handler.RunTask(new SimpleTask(string.Format(Resources.DeletingImplementation, manifestDigest), () => { DisableWriteProtection(path); string tempDir = Path.Combine(DirectoryPath, Path.GetRandomFileName()); Log.Info("Attempting atomic delete: " + path); Directory.Move(path, tempDir); Directory.Delete(tempDir, recursive: true); })); return(true); }
public static Manifest VerifyDirectory(string directory, ManifestDigest expectedDigest, ITaskHandler handler) { #region Sanity checks if (string.IsNullOrEmpty(directory)) { throw new ArgumentNullException("directory"); } if (handler == null) { throw new ArgumentNullException("handler"); } #endregion string expectedDigestValue = expectedDigest.Best; if (string.IsNullOrEmpty(expectedDigestValue)) { throw new NotSupportedException(Resources.NoKnownDigestMethod); } var format = ManifestFormat.FromPrefix(expectedDigestValue); var generator = new ManifestGenerator(directory, format) { Tag = expectedDigest }; handler.RunTask(generator); var actualManifest = generator.Result; string actualDigestValue = actualManifest.CalculateDigest(); string manifestFilePath = Path.Combine(directory, Manifest.ManifestFile); var expectedManifest = File.Exists(manifestFilePath) ? Manifest.Load(manifestFilePath, format) : null; if (actualDigestValue != expectedDigestValue) { throw new DigestMismatchException( expectedDigestValue, actualDigestValue, // Only log the complete manifests in verbose mode (handler.Verbosity > 0) ? expectedManifest : null, (handler.Verbosity > 0) ? actualManifest : null); } return(actualManifest); }
/// <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 <see langword="null"/> or empty.</exception> /// <exception cref="OperationCanceledException">The user canceled the task.</exception> /// <exception cref="IOException">There was a problem generating the manifest or detectng the executables.</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("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.Recommended.Select(format => new ManifestGenerator(ImplementationDirectory, format))) { // ... and add the resulting digest to the return value handler.RunTask(generator); newDigest.ParseID(generator.Result.CalculateDigest()); } ManifestDigest = newDigest; }
/// <summary> /// Detects <see cref="Candidates"/> in the <see cref="ImplementationDirectory"/>. /// </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 <see langword="null"/> or empty.</exception> /// <exception cref="OperationCanceledException">The user canceled the task.</exception> /// <exception cref="IOException">There was a problem generating the manifest or detectng the executables.</exception> /// <exception cref="UnauthorizedAccessException">Write access to temporary files was not permitted.</exception> public void DetectCandidates(ITaskHandler handler) { #region Sanity checks if (handler == null) throw new ArgumentNullException("handler"); if (string.IsNullOrEmpty(ImplementationDirectory)) throw new InvalidOperationException("Implementation directory is not set."); #endregion _candidates.Clear(); handler.RunTask(new SimpleTask(Resources.DetectingCandidates, () => _candidates.AddRange(Detection.ListCandidates(new DirectoryInfo(ImplementationDirectory))))); MainCandidate = _candidates.FirstOrDefault(); }
private static Manifest GenerateManifest(string path, ManifestFormat format, ITaskHandler handler) { var generator = new ManifestGenerator(path, format); handler.RunTask(generator); return generator.Result; }