public void TestRemovedLocal() { using (var apApplied = new TemporaryFlagFile("ap-applied")) using (var apUnapplied = new TemporaryFlagFile("ap-unapplied")) { var appListServer = new AppList { Entries = { new AppEntry { InterfaceUri = FeedTest.Test1Uri, AccessPoints = new AccessPointList{ Entries ={ new MockAccessPoint { ApplyFlagPath = apApplied, UnapplyFlagPath = apUnapplied } } } } } }; TestSync(SyncResetMode.None, new AppList(), appListServer.Clone(), appListServer); apApplied.Set.Should().BeFalse(because: "Locally removed access point should not be reapplied"); apUnapplied.Set.Should().BeFalse(because: "Locally removed access point should not be unapplied again"); } }
/// <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-unit-tests"); { 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 FeedUri(syncServer.ServerUri), SyncServerUsername = "******", SyncServerPassword = "******", SyncCryptoKey = CryptoKey }; using (var integrationManager = new SyncIntegrationManager(config, interfaceId => 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"); }
public void TestAddedLocal() { using var apApplied = new TemporaryFlagFile("ap-applied"); using var apNotApplied = new TemporaryFlagFile("ap-not-applied"); var appListLocal = new AppList { Entries = { new AppEntry { InterfaceUri = FeedTest.Test1Uri, AccessPoints = new AccessPointList{ Entries ={ new MockAccessPoint { ApplyFlagPath = apApplied, UnapplyFlagPath = apNotApplied } } } } } }; TestSync(SyncResetMode.None, appListLocal, new AppList(), new AppList()); apApplied.Set.Should().BeFalse(because: "Locally existing access point should not be reapplied"); apNotApplied.Set.Should().BeFalse(because: "Locally existing access point should not be removed"); }
public void TestAddedRemote() { using (var apApplied = new TemporaryFlagFile("ap-applied")) using (var apUnapplied = new TemporaryFlagFile("ap-unapplied")) { var appListRemote = new AppList { Entries = { new AppEntry { InterfaceUri = FeedTest.Test1Uri, AccessPoints = new AccessPointList{ Entries ={ new MockAccessPoint { ApplyFlagPath = apApplied, UnapplyFlagPath = apUnapplied }, new MockAccessPoint() } } } } }; TestSync(SyncResetMode.None, new AppList(), new AppList(), appListRemote); apApplied.Set.Should().BeTrue(because: "New access point should be applied"); apUnapplied.Set.Should().BeFalse(because: "New access point should not be unapplied"); } }
/// <summary> /// Checks new <see cref="AccessPoint"/> candidates for conflicts with existing ones. /// </summary> /// <param name="appList">The <see cref="AppList"/> containing the existing <see cref="AccessPoint"/>s.</param> /// <param name="accessPoints">The set of <see cref="AccessPoint"/>s candidates to check.</param> /// <param name="appEntry">The <see cref="AppEntry"/> the <paramref name="accessPoints"/> are intended for.</param> /// <exception cref="KeyNotFoundException">An <see cref="AccessPoint"/> reference to a <see cref="Capability"/> is invalid.</exception> /// <exception cref="ConflictException">One or more of the <paramref name="accessPoints"/> would cause a conflict with the existing <see cref="AccessPoint"/>s in <see cref="AppList"/>.</exception> public static void CheckForConflicts(this AppList appList, IEnumerable <AccessPoint> accessPoints, AppEntry appEntry) { #region Sanity checks if (appList == null) { throw new ArgumentNullException(nameof(appList)); } if (accessPoints == null) { throw new ArgumentNullException(nameof(accessPoints)); } if (appEntry == null) { throw new ArgumentNullException(nameof(appEntry)); } #endregion var newConflictData = accessPoints.GetConflictData(appEntry); var existingConflictData = appList.Entries.GetConflictData(); foreach ((string conflictId, var newEntry) in newConflictData) { if (existingConflictData.TryGetValue(conflictId, out var existingEntry)) { // Ignore conflicts that are actually just re-applications of existing access points if (existingEntry != newEntry) { throw ConflictException.NewConflict(existingEntry, newEntry); } } } }
/// <summary> /// Merges a new <see cref="IntegrationManagerBase.AppList"/> with the existing data. Performs a three-way merge using <see cref="_appListLastSync"/> as the base. /// </summary> /// <param name="remoteAppList">The remote <see cref="AppList"/> to merge in.</param> /// <param name="resetClient">Set to <c>true</c> to completely replace the contents of <see cref="IIntegrationManager.AppList"/> with <paramref name="remoteAppList"/> instead of merging the two.</param> private void MergeData(AppList remoteAppList, bool resetClient) { var toAdd = new List <AppEntry>(); var toRemove = new List <AppEntry>(); if (resetClient) { Merge.TwoWay( theirs: remoteAppList.Entries, mine: AppList.Entries, added: toAdd, removed: toRemove); } else { Merge.ThreeWay( reference: _appListLastSync.Entries, theirs: remoteAppList.Entries, mine: AppList.Entries, added: toAdd, removed: toRemove); } void AddApp(AppEntry prototype) => AddAppInternal(prototype, _feedRetriever); Handler.RunTask(new SimpleTask(Resources.ApplyingChanges, () => { toRemove.ApplyWithRollback(RemoveAppInternal, AddApp); toAdd.ApplyWithRollback(AddApp, RemoveAppInternal); })); }
public void TestAddedLocal() { using (var apApplied = new TemporaryFlagFile("ap-applied")) using (var apUnapplied = new TemporaryFlagFile("ap-unapplied")) { var appListLocal = new AppList { Entries = { new AppEntry { InterfaceUri = FeedTest.Test1Uri, AccessPoints = new AccessPointList{ Entries ={ new MockAccessPoint { ApplyFlagPath = apApplied, UnapplyFlagPath = apUnapplied } } } } } }; TestSync(SyncResetMode.None, appListLocal, new AppList(), new AppList()); Assert.IsFalse(apApplied.Set, "Locally existing access point should not be reapplied"); Assert.IsFalse(apUnapplied.Set, "Locally existing access point should not be removed"); } }
/// <summary> /// Checks new <see cref="AccessPoint"/> candidates for conflicts with existing ones. /// </summary> /// <param name="appList">The <see cref="AppList"/> containing the existing <see cref="AccessPoint"/>s.</param> /// <param name="accessPoints">The set of <see cref="AccessPoint"/>s candidates to check.</param> /// <param name="appEntry">The <see cref="AppEntry"/> the <paramref name="accessPoints"/> are intended for.</param> /// <exception cref="KeyNotFoundException">An <see cref="AccessPoint"/> reference to a <see cref="Store.Model.Capabilities.Capability"/> is invalid.</exception> /// <exception cref="ConflictException">One or more of the <paramref name="accessPoints"/> would cause a conflict with the existing <see cref="AccessPoint"/>s in <see cref="AppList"/>.</exception> public static void CheckForConflicts([NotNull] this AppList appList, [NotNull, ItemNotNull, InstantHandle] IEnumerable <AccessPoint> accessPoints, [NotNull] AppEntry appEntry) { #region Sanity checks if (appList == null) { throw new ArgumentNullException(nameof(appList)); } if (accessPoints == null) { throw new ArgumentNullException(nameof(accessPoints)); } if (appEntry == null) { throw new ArgumentNullException(nameof(appEntry)); } #endregion var newConflictData = accessPoints.GetConflictData(appEntry); var existingConflictData = appList.Entries.GetConflictData(); foreach (var pair in newConflictData) { ConflictData existingEntry, newEntry = pair.Value; if (existingConflictData.TryGetValue(pair.Key, out existingEntry)) { // Ignore conflicts that are actually just re-applications of existing access points if (existingEntry != newEntry) { throw ConflictException.NewConflict(existingEntry, newEntry); } } } }
public void TestResolveAppAlias() { FeedUri uri = new FeedUri("http://example.com/test1.xml"); var appList = new AppList { Entries = { new AppEntry { AccessPoints = new AccessPointList{ Entries = { new AppAlias { Name = "foobar", Command = Command.NameTest } } }, InterfaceUri = uri } } }; appList.ResolveAlias("foobar").Should().Be(uri); appList.ResolveAlias("other").Should().BeNull(); }
/// <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"); }
public void NoConflicts() { var accessPointA = new MockAccessPoint { ID = "a" }; var appEntry1 = new AppEntry { Name = "App1", InterfaceUri = FeedTest.Test1Uri, AccessPoints = new AccessPointList { Entries = { accessPointA } } }; var accessPointB = new MockAccessPoint { ID = "b" }; var appEntry2 = new AppEntry { Name = "App2", InterfaceUri = FeedTest.Test2Uri }; var appList = new AppList { Entries = { appEntry1 } }; appList.CheckForConflicts(new[] { accessPointB }, appEntry2); }
/// <summary> /// Creates a new sync manager for a custom <see cref="AppList"/> file. Used for testing. /// </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 if 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("server"); } if (feedRetriever == null) { throw new ArgumentNullException("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); } }
/// <summary> /// Merges a new <see cref="IntegrationManagerBase.AppList"/> with the existing data. /// </summary> /// <param name="remoteAppList">The remote <see cref="AppList"/> to merge in.</param> /// <param name="resetClient">Set to <see langword="true"/> to completly replace the contents of <see cref="IIntegrationManager.AppList"/> with <paramref name="remoteAppList"/> instead of merging the two.</param> /// <exception cref="OperationCanceledException">The user canceled the task.</exception> /// <exception cref="KeyNotFoundException">An <see cref="AccessPoint"/> reference to a <see cref="Store.Model.Capabilities.Capability"/> is invalid.</exception> /// <exception cref="InvalidDataException">One of the <see cref="AccessPoint"/>s or <see cref="Store.Model.Capabilities.Capability"/>s is invalid.</exception> /// <exception cref="IOException">A problem occurs while writing to the filesystem or registry.</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 occured while downloading additional data (such as icons).</exception> /// <exception cref="UnauthorizedAccessException">Write access to the filesystem or registry is not permitted.</exception> /// <remarks>Performs a three-way merge using <see cref="_appListLastSync"/> as base.</remarks> private void MergeData([NotNull] AppList remoteAppList, bool resetClient) { #region Sanity checks if (remoteAppList == null) { throw new ArgumentNullException("remoteAppList"); } #endregion var toAdd = new List <AppEntry>(); var toRemove = new List <AppEntry>(); if (resetClient) { Merge.TwoWay( theirs: remoteAppList.Entries, mine: AppList.Entries, added: toAdd, removed: toRemove); } else { Merge.ThreeWay( reference: _appListLastSync.Entries, theirs: remoteAppList.Entries, mine: AppList.Entries, added: toAdd, removed: toRemove); } Handler.RunTask(new SimpleTask(Resources.ApplyingChanges, () => { toRemove.ApplyWithRollback(RemoveAppInternal, AddAppHelper); toAdd.ApplyWithRollback(AddAppHelper, RemoveAppInternal); })); }
/// <summary> /// Creates a new integration manager using a custom <see cref="DesktopIntegration.AppList"/>. Do not use directly except for testing purposes! /// </summary> /// <param name="appListPath">The storage location of the <see cref="AppList"/> file.</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.</exception> /// <exception cref="InvalidDataException">A problem occurs while deserializing the XML data.</exception> public IntegrationManager([NotNull] string appListPath, [NotNull] ITaskHandler handler, bool machineWide = false) : base(handler) { #region Sanity checks if (appListPath == null) { throw new ArgumentNullException("appListPath"); } if (handler == null) { throw new ArgumentNullException("handler"); } #endregion MachineWide = machineWide; AppListPath = appListPath; if (File.Exists(AppListPath)) { Log.Debug("Loading AppList from: " + AppListPath); AppList = XmlStorage.LoadXml <AppList>(AppListPath); } else { Log.Debug("Creating new AppList: " + AppListPath); AppList = new AppList(); AppList.SaveXml(AppListPath); } }
/// <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); }
/// <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); }); WindowsUtils.NotifyAssocChanged(); // Notify Windows Explorer of changes WindowsUtils.BroadcastMessage(ChangedWindowMessageID); // Notify Zero Install GUIs of changes }
private static void TestClone(AppList appList) { var appList2 = appList.Clone(); // Ensure data stayed the same appList2.Should().Be(appList, because: "Cloned objects should be equal."); appList2.GetHashCode().Should().Be(appList.GetHashCode(), because: "Cloned objects' hashes should be equal."); appList2.Should().NotBeSameAs(appList, because: "Cloning should not return the same reference."); }
private static void TestClone(AppList appList) { var appList2 = appList.Clone(); // Ensure data stayed the same Assert.AreEqual(appList, appList2, "Cloned objects should be equal."); Assert.AreEqual(appList.GetHashCode(), appList2.GetHashCode(), "Cloned objects' hashes should be equal."); Assert.IsFalse(ReferenceEquals(appList, appList2), "Cloning should not return the same reference."); }
//--------------------// #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(); }
public void TestModifiedLocal() { using var apLocalApplied = new TemporaryFlagFile("ap-local-applied"); using var apLocalNotApplied = new TemporaryFlagFile("ap-local-not-applied"); using var apRemoteApplied = new TemporaryFlagFile("ap-remote-applied"); using var apRemoteNotApplied = new TemporaryFlagFile("ap-remote-not-applied"); var appListLocal = new AppList { Entries = { new AppEntry { InterfaceUri = FeedTest.Test1Uri, AutoUpdate = true, Timestamp = new DateTime(2001, 1, 1), AccessPoints = new AccessPointList{ Entries ={ new MockAccessPoint { ApplyFlagPath = apLocalApplied, UnapplyFlagPath = apLocalNotApplied } } } } } }; var appListServer = new AppList { Entries = { new AppEntry { InterfaceUri = FeedTest.Test1Uri, AutoUpdate = false, Timestamp = new DateTime(2000, 1, 1), AccessPoints = new AccessPointList{ Entries ={ new MockAccessPoint { ApplyFlagPath = apRemoteApplied, UnapplyFlagPath = apRemoteNotApplied } } } } } }; TestSync(SyncResetMode.None, appListLocal, null, appListServer); apLocalApplied.Set.Should().BeFalse(because: "Up-to-date access point should not be reapplied"); apLocalNotApplied.Set.Should().BeFalse(because: "Up-to-date access point should not be removed"); apRemoteApplied.Set.Should().BeFalse(because: "Outdated access point should not be reapplied"); apRemoteNotApplied.Set.Should().BeFalse(because: "Outdated access point should not be removed"); }
/// <inheritdoc/> protected override void AddAccessPointsInternal(AppEntry appEntry, Feed feed, IEnumerable <AccessPoint> accessPoints) { #region Sanity checks if (appEntry == null) { throw new ArgumentNullException("appEntry"); } if (feed == null) { throw new ArgumentNullException("feed"); } if (accessPoints == null) { throw new ArgumentNullException("accessPoints"); } if (appEntry.AccessPoints != null && appEntry.AccessPoints.Entries == accessPoints) { throw new ArgumentException("Must not be equal to appEntry.AccessPoints.Entries", "accessPoints"); } #endregion // Skip entries with mismatching hostname if (appEntry.Hostname != null && !Regex.IsMatch(Environment.MachineName, appEntry.Hostname)) { return; } if (appEntry.AccessPoints == null) { appEntry.AccessPoints = new AccessPointList(); } AppList.CheckForConflicts(accessPoints, appEntry); accessPoints.ApplyWithRollback( accessPoint => accessPoint.Apply(appEntry, feed, Handler, MachineWide), accessPoint => { // Don't perform rollback if the access point was already applied previously and this was only a refresh if (!appEntry.AccessPoints.Entries.Contains(accessPoint)) { accessPoint.Unapply(appEntry, MachineWide); } }); appEntry.AccessPoints.Entries.RemoveRange(accessPoints); // Replace pre-existing entries appEntry.AccessPoints.Entries.AddRange(accessPoints); appEntry.Timestamp = DateTime.UtcNow; }
private static void TestSaveLoad(AppList appList) { 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."); }
public void TestModifiedRemote() { using (var apLocalApplied = new TemporaryFlagFile("ap-local-applied")) using (var apLocalUnapplied = new TemporaryFlagFile("ap-local-unapplied")) using (var apRemoteApplied = new TemporaryFlagFile("ap-remote-applied")) using (var apRemoteUnapplied = new TemporaryFlagFile("ap-remote-unapplied")) { var appListLocal = new AppList { Entries = { new AppEntry { InterfaceUri = FeedTest.Test1Uri, AutoUpdate = true, Timestamp = new DateTime(1999, 1, 1, 0, 0, 0, DateTimeKind.Utc), AccessPoints = new AccessPointList{ Entries ={ new MockAccessPoint { ApplyFlagPath = apLocalApplied, UnapplyFlagPath = apLocalUnapplied } } } } } }; var appListServer = new AppList { Entries = { new AppEntry { InterfaceUri = FeedTest.Test1Uri, AutoUpdate = false, Timestamp = new DateTime(2001, 1, 1, 0, 0, 0, DateTimeKind.Utc), AccessPoints = new AccessPointList{ Entries ={ new MockAccessPoint { ApplyFlagPath = apRemoteApplied, UnapplyFlagPath = apRemoteUnapplied } } } } } }; TestSync(SyncResetMode.None, appListLocal, null, appListServer); apLocalApplied.Set.Should().BeFalse(because: "Outdated access point should not be reapplied"); apLocalUnapplied.Set.Should().BeTrue(because: "Outdated access point should be removed"); apRemoteApplied.Set.Should().BeTrue(because: "New access point should be applied"); apRemoteUnapplied.Set.Should().BeFalse(because: "New access point should not be unapplied"); } }
/// <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 }
/// <inheritdoc/> protected override void Finish() { try { Log.Debug("Saving AppList to: " + AppListPath); AppList.SaveXml(AppListPath); } catch (IOException) { Log.Info("Race condition encountered while saving AppList. Waiting for a moment and then retrying."); Thread.Sleep(_random.Next(250, 1500)); AppList.SaveXml(AppListPath); } WindowsUtils.NotifyAssocChanged(); // Notify Windows Explorer of changes WindowsUtils.BroadcastMessage(ChangedWindowMessageID); // Notify Zero Install GUIs of changes }
public void TestFindAppAlias() { var appAlias = new AppAlias { Name = "foobar" }; var appEntry = new AppEntry { AccessPoints = new AccessPointList { Entries = { appAlias } } }; var appList = new AppList { Entries = { appEntry } }; appList.FindAppAlias("foobar").Should().Be((appAlias, appEntry)); appList.FindAppAlias("other").Should().BeNull(); }
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 Assert.AreEqual(appList, appList2, "Serialized objects should be equal."); Assert.AreEqual(appList.GetHashCode(), appList2.GetHashCode(), "Serialized objects' hashes should be equal."); Assert.IsFalse(ReferenceEquals(appList, appList2), "Serialized objects should not return the same reference."); }
//--------------------// #region Apps /// <inheritdoc/> protected override AppEntry AddAppInternal(FeedTarget target) { // Prevent double entries if (AppList.ContainsEntry(target.Uri)) { throw new InvalidOperationException(string.Format(Resources.AppAlreadyInList, target.Feed.Name)); } // Get basic metadata and copy of capabilities from feed var appEntry = new AppEntry { InterfaceUri = target.Uri, Name = target.Feed.Name, Timestamp = DateTime.UtcNow }; appEntry.CapabilityLists.AddRange(target.Feed.CapabilityLists.CloneElements()); AppList.Entries.Add(appEntry); WriteAppDir(appEntry); return(appEntry); }
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(); }
public void TestSearch() { var appA = new AppEntry { InterfaceUri = FeedTest.Test1Uri, Name = "AppA" }; var appB = new AppEntry { InterfaceUri = FeedTest.Test2Uri, Name = "AppB" }; var lib = new AppEntry { InterfaceUri = FeedTest.Test3Uri, Name = "Lib" }; var appList = new AppList { Entries = { appA, appB, lib } }; appList.Search("").Should().Equal(appA, appB, lib); appList.Search("App").Should().Equal(appA, appB); appList.Search("AppA").Should().Equal(appA); appList.Search("AppB").Should().Equal(appB); }