/// <summary>
        /// Tests the sync logic with custom <see cref="AppList"/>s.
        /// </summary>
        /// <param name="resetMode">The <see cref="SyncResetMode"/> to pass to <see cref="SyncIntegrationManager.Sync"/>.</param>
        /// <param name="appListLocal">The current local <see cref="AppList"/>.</param>
        /// <param name="appListLast">The state of the <see cref="AppList"/> after the last successful sync.</param>
        /// <param name="appListServer">The current server-side <see cref="AppList"/>.</param>
        private void TestSync(SyncResetMode resetMode, AppList appListLocal, AppList appListLast, AppList appListServer)
        {
            appListLocal.SaveXml(_appListPath);
            if (appListLast != null) appListLast.SaveXml(_appListPath + SyncIntegrationManager.AppListLastSyncSuffix);

            using (var stream = File.Create(_appListPath + ".zip"))
                appListServer.SaveXmlZip(stream);

            using (var appListServerFile = File.OpenRead(_appListPath + ".zip"))
            using (var syncServer = new MicroServer("app-list", appListServerFile))
            {
                using (var integrationManager = new SyncIntegrationManager(_appListPath, new SyncServer {Uri = syncServer.ServerUri, Username = "******", Password = "******"}, interfaceId => new Feed(), new SilentTaskHandler()))
                    integrationManager.Sync(resetMode);

                appListServer = AppList.LoadXmlZip(syncServer.FileContent);
            }

            appListLocal = XmlStorage.LoadXml<AppList>(_appListPath);
            appListLast = XmlStorage.LoadXml<AppList>(_appListPath + SyncIntegrationManager.AppListLastSyncSuffix);
            Assert.AreEqual(appListLocal, appListServer, "Server and local data should be equal after sync");
            Assert.AreEqual(appListLocal, appListLast, "Last sync snapshot and local data should be equal after sync");
        }
Example #2
0
    /// <summary>
    /// Synchronize the <see cref="AppList"/> with the sync server and (un)apply <see cref="AccessPoint"/>s accordingly.
    /// </summary>
    /// <param name="resetMode">Controls how synchronization data is reset.</param>
    /// <exception cref="OperationCanceledException">The user canceled the task.</exception>
    /// <exception cref="InvalidDataException">A problem occurred while deserializing the XML data or the specified crypto key was wrong.</exception>
    /// <exception cref="KeyNotFoundException">An <see cref="AccessPoint"/> reference to a <see cref="Capability"/> is invalid.</exception>
    /// <exception cref="ConflictException">One or more new <see cref="AccessPoint"/> would cause a conflict with the existing <see cref="AccessPoint"/>s in <see cref="AppList"/>.</exception>
    /// <exception cref="WebException">A problem occurred while communicating with the sync server or while downloading additional data (such as icons).</exception>
    /// <exception cref="IOException">A problem occurs while writing to the filesystem or registry.</exception>
    /// <exception cref="UnauthorizedAccessException">Write access to the filesystem or registry is not permitted.</exception>
    public void Sync(SyncResetMode resetMode = SyncResetMode.None)
    {
        using var webClient = new WebClientTimeout
        {
            Credentials = new NetworkCredential(Config.SyncServerUsername, Config.SyncServerPassword),
            CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore)
        };
        var uri = new Uri(_syncServer, new Uri(MachineWide ? "app-list-machine" : "app-list", UriKind.Relative));

        ExceptionUtils.Retry<SyncRaceException>(delegate
        {
            Handler.CancellationToken.ThrowIfCancellationRequested();

            // ReSharper disable AccessToDisposedClosure
            var data = DownloadAppList(webClient, uri, resetMode);
            HandleDownloadedAppList(data, resetMode);
            UploadAppList(webClient, uri, resetMode);
            // ReSharper restore AccessToDisposedClosure
        }, maxRetries: 3);

        // Save reference point for future syncs
        AppList.SaveXml(AppListPath + AppListLastSyncSuffix);

        Handler.CancellationToken.ThrowIfCancellationRequested();
    }
Example #3
0
    /// <summary>
    /// Tests the sync logic with custom <see cref="AppList"/>s.
    /// </summary>
    /// <param name="resetMode">The <see cref="SyncResetMode"/> to pass to <see cref="SyncIntegrationManager.Sync"/>.</param>
    /// <param name="appListLocal">The current local <see cref="AppList"/>.</param>
    /// <param name="appListLast">The state of the <see cref="AppList"/> after the last successful sync.</param>
    /// <param name="appListServer">The current server-side <see cref="AppList"/>.</param>
    private static void TestSync(SyncResetMode resetMode, AppList appListLocal, AppList?appListLast, AppList appListServer)
    {
        string appListLocalPath = AppList.GetDefaultPath();

        appListLocal.SaveXml(appListLocalPath);
        appListLast?.SaveXml(appListLocalPath + SyncIntegrationManager.AppListLastSyncSuffix);

        using var appListServerPath = new TemporaryFile("0install-test-applist");
        {
            using (var stream = File.Create(appListServerPath))
                appListServer.SaveXmlZip(stream, CryptoKey);

            using (var appListServerFile = File.OpenRead(appListServerPath))
            {
                using var syncServer = new MicroServer("app-list", appListServerFile);
                var config = new Config
                {
                    SyncServer         = new(syncServer.ServerUri),
                    SyncServerUsername = "******",
                    SyncServerPassword = "******",
                    SyncCryptoKey      = CryptoKey
                };
                using (var integrationManager = new SyncIntegrationManager(config, _ => new Feed(), new SilentTaskHandler()))
                    integrationManager.Sync(resetMode);

                appListServer = AppList.LoadXmlZip(syncServer.FileContent, CryptoKey);
            }
        }

        appListLocal = XmlStorage.LoadXml <AppList>(appListLocalPath);
        appListLast  = XmlStorage.LoadXml <AppList>(appListLocalPath + SyncIntegrationManager.AppListLastSyncSuffix);
        appListServer.Should().Be(appListLocal, because: "Server and local data should be equal after sync");
        appListLast.Should().Be(appListLocal, because: "Last sync snapshot and local data should be equal after sync");
    }
Example #4
0
    /// <inheritdoc/>
    protected override void Finish()
    {
        Log.Debug("Saving AppList to: " + AppListPath);
        // Retry to handle race conditions with read-only access to the file
        ExceptionUtils.Retry <IOException>(delegate { AppList.SaveXml(AppListPath); });

        if (WindowsUtils.IsWindows)
        {
            WindowsUtils.NotifyAssocChanged();                     // Notify Windows Explorer of changes
            WindowsUtils.BroadcastMessage(ChangedWindowMessageID); // Notify Zero Install GUIs of changes
        }
    }
Example #5
0
    /// <summary>
    /// Creates a new sync manager. Performs Mutex-based locking!
    /// </summary>
    /// <param name="config">Configuration for communicating with a sync server.</param>
    /// <param name="feedRetriever">Callback method used to retrieve additional <see cref="Feed"/>s on demand.</param>
    /// <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 operations machine-wide instead of just for the current user.</param>
    /// <exception cref="IOException">A problem occurred while accessing the <see cref="AppList"/> file.</exception>
    /// <exception cref="UnauthorizedAccessException">Read or write access to the <see cref="AppList"/> file is not permitted or another desktop integration class is currently active.</exception>
    /// <exception cref="InvalidDataException">A problem occurred while deserializing the XML data.</exception>
    public SyncIntegrationManager(Config config, Converter<FeedUri, Feed> feedRetriever, ITaskHandler handler, bool machineWide = false)
        : base(config, handler, machineWide)
    {
        if (!config.IsSyncConfigured) throw new InvalidDataException(Resources.PleaseConfigSync);
        _syncServer = config.SyncServer.EnsureTrailingSlash();

        _feedRetriever = feedRetriever ?? throw new ArgumentNullException(nameof(feedRetriever));

        if (File.Exists(AppListPath + AppListLastSyncSuffix)) _appListLastSync = XmlStorage.LoadXml<AppList>(AppListPath + AppListLastSyncSuffix);
        else
        {
            _appListLastSync = new AppList();
            _appListLastSync.SaveXml(AppListPath + AppListLastSyncSuffix);
        }
    }
Example #6
0
    /// <summary>
    /// Creates a new integration manager using the default <see cref="DesktopIntegration.AppList"/> (creating a new one if missing). Performs Mutex-based locking!
    /// </summary>
    /// <param name="config">User settings controlling network behaviour.</param>
    /// <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 operations machine-wide instead of just for the current user.</param>
    /// <exception cref="IOException">A problem occurred while accessing the <see cref="AppList"/> file.</exception>
    /// <exception cref="UnauthorizedAccessException">Read or write access to the <see cref="AppList"/> file is not permitted or another desktop integration class is currently active.</exception>
    /// <exception cref="InvalidDataException">A problem occurred while deserializing the XML data.</exception>
    public IntegrationManager(Config config, ITaskHandler handler, bool machineWide = false)
        : base(handler, machineWide)
    {
        Config = config ?? throw new ArgumentNullException(nameof(config));

        try
        {
            AcquireMutex();
        }
        catch (TimeoutException)
        {
            throw new UnauthorizedAccessException(Resources.IntegrationMutex);
        }

        try
        {
            AppListPath = AppList.GetDefaultPath(machineWide);
            if (File.Exists(AppListPath))
            {
                Log.Debug("Loading AppList for IntegrationManager from: " + AppListPath);
                AppList = XmlStorage.LoadXml <AppList>(AppListPath);
            }
            else
            {
                Log.Debug("Creating new AppList for IntegrationManager: " + AppListPath);
                AppList = new AppList();
                AppList.SaveXml(AppListPath);
            }
        }
        #region Error handling
        catch
        {
            // Avoid abandoned mutexes
            Dispose();
            throw;
        }
        #endregion
    }
        /// <summary>
        /// Creates a new integration manager using the default <see cref="DesktopIntegration.AppList"/> (creating a new one if missing). Performs Mutex-based locking.
        /// </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 operations machine-wide instead of just for the current user.</param>
        /// <exception cref="IOException">A problem occurs while accessing the <see cref="AppList"/> file.</exception>
        /// <exception cref="UnauthorizedAccessException">Read or write access to the <see cref="AppList"/> file is not permitted or if another desktop integration class is currently active.</exception>
        /// <exception cref="InvalidDataException">A problem occurs while deserializing the XML data.</exception>
        public IntegrationManager([NotNull] ITaskHandler handler, bool machineWide = false)
            : base(handler)
        {
            #region Sanity checks
            if (handler == null) throw new ArgumentNullException("handler");
            #endregion

            MachineWide = machineWide;
            _mutex = AquireMutex();

            try
            {
                AppListPath = AppList.GetDefaultPath(machineWide);
                if (File.Exists(AppListPath))
                {
                    Log.Debug("Loading AppList for IntegrationManager from: " + AppListPath);
                    AppList = XmlStorage.LoadXml<AppList>(AppListPath);
                }
                else
                {
                    Log.Debug("Creating new AppList for IntegrationManager: " + AppListPath);
                    AppList = new AppList();
                    AppList.SaveXml(AppListPath);
                }
            }
                #region Error handling
            catch
            {
                // Avoid abandoned mutexes
                _mutex.ReleaseMutex();
                _mutex.Close();
                throw;
            }
            #endregion
        }
        /// <summary>
        /// Creates a new integration manager using the default <see cref="DesktopIntegration.AppList"/> (creating a new one if missing). Performs Mutex-based locking!
        /// </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 operations machine-wide instead of just for the current user.</param>
        /// <exception cref="IOException">A problem occurs while accessing the <see cref="AppList"/> file.</exception>
        /// <exception cref="UnauthorizedAccessException">Read or write access to the <see cref="AppList"/> file is not permitted or another desktop integration class is currently active.</exception>
        /// <exception cref="InvalidDataException">A problem occurs while deserializing the XML data.</exception>
        public IntegrationManager([NotNull] ITaskHandler handler, bool machineWide = false)
            : base(handler, machineWide)
        {
            #region Sanity checks
            if (handler == null) throw new ArgumentNullException(nameof(handler));
            #endregion

            try
            {
                AquireMutex();
            }
                #region Error handling
            catch (UnauthorizedAccessException)
            {
                // Replace exception to add more context
                throw new UnauthorizedAccessException(Resources.IntegrationMutex);
            }
            #endregion

            try
            {
                AppListPath = AppList.GetDefaultPath(machineWide);
                if (File.Exists(AppListPath))
                {
                    Log.Debug("Loading AppList for IntegrationManager from: " + AppListPath);
                    AppList = XmlStorage.LoadXml<AppList>(AppListPath);
                }
                else
                {
                    Log.Debug("Creating new AppList for IntegrationManager: " + AppListPath);
                    AppList = new AppList();
                    AppList.SaveXml(AppListPath);
                }
            }
                #region Error handling
            catch
            {
                // Avoid abandoned mutexes
                Dispose();
                throw;
            }
            #endregion
        }
        /// <summary>
        /// Creates a new sync manager for a custom <see cref="AppList"/> file. Used for testing. Uses no mutex!
        /// </summary>
        /// <param name="appListPath">The storage location of the <see cref="AppList"/> file.</param>
        /// <param name="server">Access information for the sync server.</param>
        /// <param name="feedRetriever">Callback method used to retrieve additional <see cref="Feed"/>s on demand.</param>
        /// <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 operations machine-wide instead of just for the current user.</param>
        /// <exception cref="IOException">A problem occurs while accessing the <see cref="AppList"/> file.</exception>
        /// <exception cref="UnauthorizedAccessException">Read or write access to the <see cref="AppList"/> file is not permitted or another desktop integration class is currently active.</exception>
        /// <exception cref="InvalidDataException">A problem occurs while deserializing the XML data.</exception>
        public SyncIntegrationManager([NotNull] string appListPath, SyncServer server, [NotNull] Converter<FeedUri, Feed> feedRetriever, [NotNull] ITaskHandler handler, bool machineWide = false)
            : base(appListPath, handler, machineWide)
        {
            #region Sanity checks
            if (server.Uri == null) throw new ArgumentNullException(nameof(server));
            if (feedRetriever == null) throw new ArgumentNullException(nameof(feedRetriever));
            #endregion

            _server = server;
            _feedRetriever = feedRetriever;

            if (File.Exists(AppListPath + AppListLastSyncSuffix)) _appListLastSync = XmlStorage.LoadXml<AppList>(AppListPath + AppListLastSyncSuffix);
            else
            {
                _appListLastSync = new AppList();
                _appListLastSync.SaveXml(AppListPath + AppListLastSyncSuffix);
            }
        }
Example #10
0
        private static void TestSaveLoad(AppList appList)
        {
            Assert.That(appList, Is.XmlSerializable);

            AppList appList2;
            using (var tempFile = new TemporaryFile("0install-unit-tests"))
            {
                // Write and read file
                appList.SaveXml(tempFile);
                appList2 = XmlStorage.LoadXml<AppList>(tempFile);
            }

            // Ensure data stayed the same
            appList2.Should().Be(appList, because: "Serialized objects should be equal.");
            appList2.GetHashCode().Should().Be(appList.GetHashCode(), because: "Serialized objects' hashes should be equal.");
            appList2.Should().NotBeSameAs(appList, because: "Serialized objects should not return the same reference.");
        }