/// <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");
        }
示例#2
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");
    }
示例#3
0
    private byte[] DownloadAppList(WebClient webClient, Uri uri, SyncResetMode resetMode)
    {
        if (resetMode == SyncResetMode.Server) return Array.Empty<byte>();

        if (uri.IsFile)
        {
            try
            {
                return File.ReadAllBytes(uri.LocalPath);
            }
            #region Error handling
            catch (FileNotFoundException)
            {
                return Array.Empty<byte>();
            }
            #endregion
        }
        else
        {
            try
            {
                byte[] data = null!;
                Handler.RunTask(new SimpleTask(Resources.SyncDownloading, () => { data = webClient.DownloadData(uri); }));
                return data;
            }
            #region Error handling
            catch (WebException ex) when (ex.Response is HttpWebResponse {StatusCode: HttpStatusCode.Unauthorized})
        /// <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
                    }, 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");
        }
示例#5
0
        private void Sync(SyncResetMode resetMode = SyncResetMode.None)
        {
            var services = new ServiceLocator(new DialogTaskHandler(this));

            using var sync = new SyncIntegrationManager(SyncConfig.From(_config), services.FeedManager.GetFresh, services.Handler, _machineWide);
            sync.Sync(resetMode);
        }
示例#6
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();
    }
        /// <summary>
        /// Tests the sync logic with pre-defined <see cref="AppList"/>s.
        /// local add: appEntry1, local remove: appEntry2, remote add: appEntry3, remote remove: appEntry4
        /// </summary>
        /// <param name="resetMode">The <see cref="SyncResetMode"/> to pass to <see cref="SyncIntegrationManager.Sync"/>.</param>
        /// <param name="ap1Applied">The flag file used to indicate that <see cref="MockAccessPoint.Apply"/> was called for appEntry1.</param>
        /// <param name="ap1NotApplied">The flag file used to indicate that <see cref="MockAccessPoint.Unapply"/> was called for appEntry1.</param>
        /// <param name="ap2Applied">The flag file used to indicate that <see cref="MockAccessPoint.Apply"/> was called for appEntry2.</param>
        /// <param name="ap2NotApplied">The flag file used to indicate that <see cref="MockAccessPoint.Unapply"/> was called for appEntry2.</param>
        /// <param name="ap3Applied">The flag file used to indicate that <see cref="MockAccessPoint.Apply"/> was called for appEntry3.</param>
        /// <param name="ap3NotApplied">The flag file used to indicate that <see cref="MockAccessPoint.Unapply"/> was called for appEntry3.</param>
        /// <param name="ap4Applied">The flag file used to indicate that <see cref="MockAccessPoint.Apply"/> was called for appEntry4.</param>
        /// <param name="ap4NotApplied">The flag file used to indicate that <see cref="MockAccessPoint.Unapply"/> was called for appEntry4.</param>
        private static void TestSync(SyncResetMode resetMode,
                                     TemporaryFlagFile ap1Applied,
                                     TemporaryFlagFile ap1NotApplied,
                                     TemporaryFlagFile ap2Applied,
                                     TemporaryFlagFile ap2NotApplied,
                                     TemporaryFlagFile ap3Applied,
                                     TemporaryFlagFile ap3NotApplied,
                                     TemporaryFlagFile ap4Applied,
                                     TemporaryFlagFile ap4NotApplied)
        {
            var appEntry1 = new AppEntry
            {
                InterfaceUri = FeedTest.Test1Uri,
                AccessPoints = new AccessPointList {
                    Entries = { new MockAccessPoint {
                                    ApplyFlagPath = ap1Applied, UnapplyFlagPath = ap1NotApplied
                                } }
                }
            };
            var appEntry2 = new AppEntry
            {
                InterfaceUri = FeedTest.Test2Uri,
                AccessPoints = new AccessPointList {
                    Entries = { new MockAccessPoint {
                                    ApplyFlagPath = ap2Applied, UnapplyFlagPath = ap2NotApplied
                                } }
                }
            };
            var appEntry3 = new AppEntry
            {
                InterfaceUri = FeedTest.Test3Uri,
                AccessPoints = new AccessPointList {
                    Entries = { new MockAccessPoint {
                                    ApplyFlagPath = ap3Applied, UnapplyFlagPath = ap3NotApplied
                                } }
                }
            };
            var appEntry4 = new AppEntry
            {
                InterfaceUri = new FeedUri("http://example.com/test4.xml"),
                AccessPoints = new AccessPointList {
                    Entries = { new MockAccessPoint {
                                    ApplyFlagPath = ap4Applied, UnapplyFlagPath = ap4NotApplied
                                } }
                }
            };
            var appListLocal = new AppList {
                Entries = { appEntry1, appEntry4 }
            };
            var appListLast = new AppList {
                Entries = { appEntry2, appEntry4 }
            };
            var appListServer = new AppList {
                Entries = { appEntry2, appEntry3 }
            };

            TestSync(resetMode, appListLocal, appListLast, appListServer);
        }
示例#8
0
        //--------------------//

        #region Sync
        /// <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="WebException">A problem occured 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)
        {
            if (!_server.IsValid)
            {
                throw new InvalidDataException(Resources.PleaseConfigSync);
            }

            var appListUri = new Uri(_server.Uri, new Uri(MachineWide ? "app-list-machine" : "app-list", UriKind.Relative));

            using (var webClient = new WebClientTimeout
            {
                Credentials = _server.Credentials,
                CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore)
            })
            {
                ExceptionUtils.Retry <WebRaceConditionException>(delegate
                {
                    Handler.CancellationToken.ThrowIfCancellationRequested();

                    byte[] appListData;
                    try
                    {
                        appListData = DownloadAppList(appListUri, webClient, resetMode);
                    }
                    #region Error handling
                    catch (WebException ex)
                        when(ex.Status == WebExceptionStatus.ProtocolError && (ex.Response as HttpWebResponse)?.StatusCode == HttpStatusCode.Unauthorized)
                        {
                            Handler.CancellationToken.ThrowIfCancellationRequested();
                            throw new WebException(Resources.SyncCredentialsInvalid, ex, ex.Status, ex.Response);
                        }
                    #endregion

                    HandleDownloadedAppList(resetMode, appListData);

                    try
                    {
                        UploadAppList(appListUri, webClient, resetMode);
                    }
                    #region Error handling
                    catch (WebException ex)
                        when(ex.Status == WebExceptionStatus.ProtocolError && (ex.Response as HttpWebResponse)?.StatusCode == HttpStatusCode.PreconditionFailed)
                        {
                            Handler.CancellationToken.ThrowIfCancellationRequested();
                            throw new WebRaceConditionException(ex);
                        }
                    #endregion
                }, maxRetries: 3);
            }

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

            Handler.CancellationToken.ThrowIfCancellationRequested();
        }
示例#9
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 an XML file 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)
    {
        var uri         = new Uri(_syncServer, new Uri(MachineWide ? "app-list-machine" : "app-list", UriKind.Relative));
        var credentials = new NetworkCredential(Config.SyncServerUsername, Config.SyncServerPassword);

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

            var(data, etag) = resetMode switch
            {
                SyncResetMode.Server => default,
        private void HandleDownloadedAppList(SyncResetMode resetMode, [NotNull] byte[] appListData)
        {
            if (appListData.Length == 0)
            {
                return;
            }

            AppList serverList;

            try
            {
                serverList = AppList.LoadXmlZip(new MemoryStream(appListData), _cryptoKey);
            }
            #region Error handling
            catch (ZipException ex)
            {
                // Wrap exception to add context information
                if (ex.Message == "Invalid password")
                {
                    throw new InvalidDataException(Resources.SyncCryptoKeyInvalid);
                }
                throw new InvalidDataException(Resources.SyncServerDataDamaged, ex);
            }
            #endregion

            Handler.CancellationToken.ThrowIfCancellationRequested();
            try
            {
                MergeData(serverList, resetClient: (resetMode == SyncResetMode.Client));
            }
            catch (KeyNotFoundException ex)
            {
                // Wrap exception since only certain exception types are allowed
                throw new InvalidDataException(ex.Message, ex);
            }
            finally
            {
                Finish();
            }

            Handler.CancellationToken.ThrowIfCancellationRequested();
        }
示例#11
0
        /// <summary>
        /// Upload the encrypted AppList back to the server (unless the client was reset)
        /// </summary>
        private void UploadAppList(WebClient webClient, Uri uri, SyncResetMode resetMode)
        {
            if (resetMode == SyncResetMode.Client)
            {
                return;
            }

            var memoryStream = new MemoryStream();

            AppList.SaveXmlZip(memoryStream, Config.SyncCryptoKey);

            if (uri.IsFile)
            {
                using var fileStream = File.OpenWrite(uri.LocalPath);
                memoryStream.CopyToEx(fileStream);
            }
            else
            {
                // Prevent "lost updates" (race conditions) with HTTP ETags
                if (resetMode == SyncResetMode.None && (uri.Scheme == "http" || uri.Scheme == "https"))
                {
                    if (!string.IsNullOrEmpty(webClient.ResponseHeaders[HttpResponseHeader.ETag]))
                    {
                        webClient.Headers[HttpRequestHeader.IfMatch] = webClient.ResponseHeaders[HttpResponseHeader.ETag];
                    }
                }

                try
                {
                    Handler.RunTask(new SimpleTask(Resources.SyncUploading, () => webClient.UploadData(uri, "PUT", memoryStream.ToArray())));
                }
                #region Error handling
                catch (WebException ex) when(ex.Status == WebExceptionStatus.ProtocolError && (ex.Response as HttpWebResponse)?.StatusCode == HttpStatusCode.PreconditionFailed)
                {
                    Handler.CancellationToken.ThrowIfCancellationRequested();
                    throw new SyncRaceException(ex);
                }
                #endregion
            }
        }
示例#12
0
        private byte[] DownloadAppList(WebClient webClient, Uri uri, SyncResetMode resetMode)
        {
            if (resetMode == SyncResetMode.Server)
            {
                return(new byte[0]);
            }

            if (uri.IsFile)
            {
                try
                {
                    return(File.ReadAllBytes(uri.LocalPath));
                }
                #region Error handling
                catch (FileNotFoundException)
                {
                    return(new byte[0]);
                }
                #endregion
            }
            else
            {
                try
                {
                    byte[] data = null !;
                    Handler.RunTask(new SimpleTask(Resources.SyncDownloading, () => { data = webClient.DownloadData(uri); }));
                    return(data);
                }
                #region Error handling
                catch (WebException ex) when(ex.Status == WebExceptionStatus.ProtocolError && (ex.Response as HttpWebResponse)?.StatusCode == HttpStatusCode.Unauthorized)
                {
                    Handler.CancellationToken.ThrowIfCancellationRequested();
                    throw new WebException(Resources.SyncCredentialsInvalid, ex, ex.Status, ex.Response);
                }
                #endregion
            }
        }
        /// <summary>
        /// Upload the encrypted AppList back to the server (unless the client was reset)
        /// </summary>
        private void UploadAppList([NotNull] Uri appListUri, [NotNull] WebClientTimeout webClient, SyncResetMode resetMode)
        {
            if (resetMode == SyncResetMode.Client) return;

            var memoryStream = new MemoryStream();
            AppList.SaveXmlZip(memoryStream, _cryptoKey);

            // Prevent "lost updates" (race conditions) with HTTP ETags
            if (resetMode == SyncResetMode.None && (appListUri.Scheme == "http" || appListUri.Scheme == "https"))
            {
                if (!string.IsNullOrEmpty(webClient.ResponseHeaders[HttpResponseHeader.ETag]))
                    webClient.Headers[HttpRequestHeader.IfMatch] = webClient.ResponseHeaders[HttpResponseHeader.ETag];
            }

            Handler.RunTask(new SimpleTask(Resources.SyncUploading, () => webClient.UploadData(appListUri, "PUT", memoryStream.ToArray())));
        }
        private void HandleDownloadedAppList(SyncResetMode resetMode, [NotNull] byte[] appListData)
        {
            if (appListData.Length == 0) return;

            AppList serverList;
            try
            {
                serverList = AppList.LoadXmlZip(new MemoryStream(appListData), _cryptoKey);
            }
                #region Error handling
            catch (ZipException ex)
            {
                // Wrap exception to add context information
                if (ex.Message == "Invalid password") throw new InvalidDataException(Resources.SyncCryptoKeyInvalid);
                throw new InvalidDataException(Resources.SyncServerDataDamaged, ex);
            }
            #endregion

            Handler.CancellationToken.ThrowIfCancellationRequested();
            try
            {
                MergeData(serverList, resetClient: (resetMode == SyncResetMode.Client));
            }
            catch (KeyNotFoundException ex)
            {
                // Wrap exception since only certain exception types are allowed
                throw new InvalidDataException(ex.Message, ex);
            }
            finally
            {
                Finish();
            }

            Handler.CancellationToken.ThrowIfCancellationRequested();
        }
        private byte[] DownloadAppList([NotNull] Uri appListUri, [NotNull] WebClientTimeout webClient, SyncResetMode resetMode)
        {
            if (resetMode == SyncResetMode.Server) return new byte[0];

            byte[] data = null;
            Handler.RunTask(new SimpleTask(Resources.SyncDownloading, () => { data = webClient.DownloadData(appListUri); }));
            return data;
        }
        //--------------------//

        #region Sync
        /// <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="WebException">A problem occured 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)
        {
            if (!_server.IsValid) throw new InvalidDataException(Resources.PleaseConfigSync);

            var appListUri = new Uri(_server.Uri, new Uri(MachineWide ? "app-list-machine" : "app-list", UriKind.Relative));
            using (var webClient = new WebClientTimeout
            {
                Credentials = _server.Credentials,
                CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore)
            })
            {
                ExceptionUtils.Retry<WebRaceConditionException>(delegate
                {
                    Handler.CancellationToken.ThrowIfCancellationRequested();

                    byte[] appListData;
                    try
                    {
                        appListData = DownloadAppList(appListUri, webClient, resetMode);
                    }
                        #region Error handling
                    catch (WebException ex)
                        when (ex.Status == WebExceptionStatus.ProtocolError && (ex.Response as HttpWebResponse)?.StatusCode == HttpStatusCode.Unauthorized)
                    {
                        Handler.CancellationToken.ThrowIfCancellationRequested();
                        throw new WebException(Resources.SyncCredentialsInvalid, ex, ex.Status, ex.Response);
                    }
                    #endregion

                    HandleDownloadedAppList(resetMode, appListData);

                    try
                    {
                        UploadAppList(appListUri, webClient, resetMode);
                    }
                        #region Error handling
                    catch (WebException ex)
                        when (ex.Status == WebExceptionStatus.ProtocolError && (ex.Response as HttpWebResponse)?.StatusCode == HttpStatusCode.PreconditionFailed)
                    {
                        Handler.CancellationToken.ThrowIfCancellationRequested();
                        throw new WebRaceConditionException(ex);
                    }
                    #endregion
                }, maxRetries: 3);
            }

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

            Handler.CancellationToken.ThrowIfCancellationRequested();
        }
        /// <summary>
        /// Upload the encrypted AppList back to the server (unless the client was reset)
        /// </summary>
        private void UploadAppList([NotNull] Uri appListUri, [NotNull] WebClientTimeout webClient, SyncResetMode resetMode)
        {
            if (resetMode == SyncResetMode.Client)
            {
                return;
            }

            var memoryStream = new MemoryStream();

            AppList.SaveXmlZip(memoryStream, _cryptoKey);

            // Prevent "lost updates" (race conditions) with HTTP ETags
            if (resetMode == SyncResetMode.None && (appListUri.Scheme == "http" || appListUri.Scheme == "https"))
            {
                if (!string.IsNullOrEmpty(webClient.ResponseHeaders[HttpResponseHeader.ETag]))
                {
                    webClient.Headers[HttpRequestHeader.IfMatch] = webClient.ResponseHeaders[HttpResponseHeader.ETag];
                }
            }

            Handler.RunTask(new SimpleTask(Resources.SyncUploading, () => webClient.UploadData(appListUri, "PUT", memoryStream.ToArray())));
        }
        private byte[] DownloadAppList([NotNull] Uri appListUri, [NotNull] WebClientTimeout webClient, SyncResetMode resetMode)
        {
            if (resetMode == SyncResetMode.Server)
            {
                return(new byte[0]);
            }

            byte[] data = null;
            Handler.RunTask(new SimpleTask(Resources.SyncDownloading, () => { data = webClient.DownloadData(appListUri); }));
            return(data);
        }
        //--------------------//

        #region Sync
        /// <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 if the specified crypto key was wrong.</exception>
        /// <exception cref="WebException">A problem occured 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)
        {
            var appListUri = new Uri(_server.Uri, new Uri(MachineWide ? "app-list-machine" : "app-list", UriKind.Relative));

            using (var webClient = new WebClientTimeout
            {
                Credentials = _server.Credentials,
                CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore)
            })
            {
                ExceptionUtils.Retry <TimeoutException>(delegate
                {
                    Handler.CancellationToken.ThrowIfCancellationRequested();

                    byte[] appListData;
                    try
                    {
                        appListData = DownloadAppList(appListUri, webClient, resetMode);
                    }
                    #region Error handling
                    catch (WebException ex)
                    {
                        switch (ex.Status)
                        {
                        case WebExceptionStatus.ProtocolError:
                            var response = ex.Response as HttpWebResponse;
                            if (response != null && response.StatusCode == HttpStatusCode.Unauthorized)
                            {
                                throw new WebException(Resources.SyncCredentialsInvalid, ex);
                            }
                            else
                            {
                                throw;
                            }

                        case WebExceptionStatus.Timeout:
                            throw new TimeoutException(ex.Message, ex);

                        default:
                            if (ex.InnerException != null && ex.InnerException.InnerException is FileNotFoundException)
                            {
                                appListData = new byte[0];
                            }
                            else
                            {
                                throw;
                            }
                            break;
                        }
                    }
                    #endregion

                    HandleDownloadedAppList(resetMode, appListData);

                    try
                    {
                        UploadAppList(appListUri, webClient, resetMode);
                    }
                    #region Error handling
                    catch (WebException ex)
                    {
                        if (ex.Status == WebExceptionStatus.ProtocolError)
                        {
                            var response = ex.Response as HttpWebResponse;
                            if (response != null && response.StatusCode == HttpStatusCode.PreconditionFailed)
                            {
                                Handler.CancellationToken.ThrowIfCancellationRequested();
                                throw new TimeoutException("Race condition encountered while syncing.");
                            }
                        }
                        else
                        {
                            throw;
                        }
                    }
                    #endregion
                }, maxRetries: 3);
            }

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

            Handler.CancellationToken.ThrowIfCancellationRequested();
        }
        /// <summary>
        /// Tests the sync logic with pre-defined <see cref="AppList"/>s.
        /// local add: appEntry1, local remove: appEntry2, remote add: appEntry3, remote remove: appEntry4
        /// </summary>
        /// <param name="resetMode">The <see cref="SyncResetMode"/> to pass to <see cref="SyncIntegrationManager.Sync"/>.</param>
        /// <param name="ap1Applied">The flag file used to indicate that <see cref="MockAccessPoint.Apply"/> was called for appEntry1.</param>
        /// <param name="ap1Unapplied">The flag file used to indicate that <see cref="MockAccessPoint.Unapply"/> was called for appEntry1.</param>
        /// <param name="ap2Applied">The flag file used to indicate that <see cref="MockAccessPoint.Apply"/> was called for appEntry2.</param>
        /// <param name="ap2Unapplied">The flag file used to indicate that <see cref="MockAccessPoint.Unapply"/> was called for appEntry2.</param>
        /// <param name="ap3Applied">The flag file used to indicate that <see cref="MockAccessPoint.Apply"/> was called for appEntry3.</param>
        /// <param name="ap3Unapplied">The flag file used to indicate that <see cref="MockAccessPoint.Unapply"/> was called for appEntry3.</param>
        /// <param name="ap4Applied">The flag file used to indicate that <see cref="MockAccessPoint.Apply"/> was called for appEntry4.</param>
        /// <param name="ap4Unapplied">The flag file used to indicate that <see cref="MockAccessPoint.Unapply"/> was called for appEntry4.</param>
        private void TestSync(SyncResetMode resetMode,
            TemporaryFlagFile ap1Applied, TemporaryFlagFile ap1Unapplied,
            TemporaryFlagFile ap2Applied, TemporaryFlagFile ap2Unapplied,
            TemporaryFlagFile ap3Applied, TemporaryFlagFile ap3Unapplied,
            TemporaryFlagFile ap4Applied, TemporaryFlagFile ap4Unapplied)
        {
            var appEntry1 = new AppEntry
            {
                InterfaceUri = FeedTest.Test1Uri,
                AccessPoints = new AccessPointList {Entries = {new MockAccessPoint {ApplyFlagPath = ap1Applied, UnapplyFlagPath = ap1Unapplied}}}
            };
            var appEntry2 = new AppEntry
            {
                InterfaceUri = FeedTest.Test2Uri,
                AccessPoints = new AccessPointList {Entries = {new MockAccessPoint {ApplyFlagPath = ap2Applied, UnapplyFlagPath = ap2Unapplied}}}
            };
            var appEntry3 = new AppEntry
            {
                InterfaceUri = FeedTest.Test3Uri,
                AccessPoints = new AccessPointList {Entries = {new MockAccessPoint {ApplyFlagPath = ap3Applied, UnapplyFlagPath = ap3Unapplied}}}
            };
            var appEntry4 = new AppEntry
            {
                InterfaceUri = new FeedUri("http://0install.de/feeds/test/test4.xml"),
                AccessPoints = new AccessPointList {Entries = {new MockAccessPoint {ApplyFlagPath = ap4Applied, UnapplyFlagPath = ap4Unapplied}}}
            };
            var appListLocal = new AppList {Entries = {appEntry1, appEntry4}};
            var appListLast = new AppList {Entries = {appEntry2, appEntry4}};
            var appListServer = new AppList {Entries = {appEntry2, appEntry3}};

            TestSync(resetMode, appListLocal, appListLast, appListServer);
        }
示例#21
0
 /// <inheritdoc/>
 public SyncApps([NotNull] ICommandHandler handler)
     : base(handler)
 {
     Options.Add("reset=", () => Resources.OptionSyncReset, (SyncResetMode mode) => _syncResetMode = mode);
 }
示例#22
0
 /// <inheritdoc/>
 public SyncApps([NotNull] ICommandHandler handler) : base(handler)
 {
     Options.Add("reset=", () => Resources.OptionSyncReset, (SyncResetMode mode) => _syncResetMode = mode);
 }
        /// <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 if the specified crypto key was wrong.</exception>
        /// <exception cref="WebException">A problem occured 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)
        {
            if (!_server.IsValid) throw new InvalidDataException(Resources.PleaseConfigSync);

            var appListUri = new Uri(_server.Uri, new Uri(MachineWide ? "app-list-machine" : "app-list", UriKind.Relative));
            using (var webClient = new WebClientTimeout
            {
                Credentials = _server.Credentials,
                CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore)
            })
            {
                ExceptionUtils.Retry<TimeoutException>(delegate
                {
                    Handler.CancellationToken.ThrowIfCancellationRequested();

                    byte[] appListData;
                    try
                    {
                        appListData = DownloadAppList(appListUri, webClient, resetMode);
                    }
                        #region Error handling
                    catch (WebException ex)
                    {
                        switch (ex.Status)
                        {
                            case WebExceptionStatus.ProtocolError:
                                var response = ex.Response as HttpWebResponse;
                                if (response != null && response.StatusCode == HttpStatusCode.Unauthorized)
                                    throw new WebException(Resources.SyncCredentialsInvalid, ex);
                                else throw;

                            case WebExceptionStatus.Timeout:
                                throw new TimeoutException(ex.Message, ex);

                            default:
                                if (ex.InnerException != null && ex.InnerException.InnerException is FileNotFoundException) appListData = new byte[0];
                                else throw;
                                break;
                        }
                    }
                    #endregion

                    HandleDownloadedAppList(resetMode, appListData);

                    try
                    {
                        UploadAppList(appListUri, webClient, resetMode);
                    }
                        #region Error handling
                    catch (WebException ex)
                    {
                        if (ex.Status == WebExceptionStatus.ProtocolError)
                        {
                            var response = ex.Response as HttpWebResponse;
                            if (response != null && response.StatusCode == HttpStatusCode.PreconditionFailed)
                            {
                                Handler.CancellationToken.ThrowIfCancellationRequested();
                                throw new TimeoutException("Race condition encountered while syncing.");
                            }
                        }
                        else throw;
                    }
                    #endregion
                }, maxRetries: 3);
            }

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

            Handler.CancellationToken.ThrowIfCancellationRequested();
        }