/// <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"); }
/// <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"); }
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"); }
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); }
/// <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); }
//--------------------// #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> /// 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(); }
/// <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 } }
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); }
/// <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(); }