private void HandleJoinTeamCollection(ApiRequest request) { try { FolderTeamCollection.JoinCollectionTeam(); ReactDialog.CloseCurrentModal(); Analytics.Track("TeamCollectionJoin", new Dictionary <string, string>() { { "CollectionId", _settings?.CollectionId }, { "CollectionName", _settings?.CollectionName }, { "Backend", _tcManager?.CurrentCollection?.GetBackendType() }, { "User", CurrentUser } }); request.PostSucceeded(); } catch (Exception e) { // Not sure what to do here: joining the collection crashed. Logger.WriteError("TeamCollectionApi.HandleJoinTeamCollection() crashed", e); var msg = LocalizationManager.GetString("TeamCollection.ErrorJoining", "Could not join Team Collection"); ErrorReport.NotifyUserOfProblem(e, msg); NonFatalProblem.ReportSentryOnly(e, $"Something went wrong for {request.LocalPath()}"); // Since we have already informed the user above, it is better to just report a success here. // Otherwise, they will also get a toast. request.PostSucceeded(); } }
public void Checkin_RenamedBook_DeletesOriginal_NoTombstone() { using (var collectionFolder = new TemporaryFolder("Checkin_RenamedBook_DeletesOriginal_Collection")) { using (var repoFolder = new TemporaryFolder("Checkin_RenamedBook_DeletesOriginal_Shared")) { var mockTcManager = new Mock <ITeamCollectionManager>(); TeamCollectionManager.ForceCurrentUserForTests("*****@*****.**"); var tc = new FolderTeamCollection(mockTcManager.Object, collectionFolder.FolderPath, repoFolder.FolderPath); tc.CollectionId = Bloom.TeamCollection.TeamCollection.GenerateCollectionId(); var oldFolderPath = SyncAtStartupTests.MakeFakeBook(collectionFolder.FolderPath, "old name", "book content"); tc.PutBook(oldFolderPath); tc.AttemptLock("old name"); SyncAtStartupTests.SimulateRename(tc, "old name", "middle name"); SyncAtStartupTests.SimulateRename(tc, "middle name", "new name"); tc.PutBook(Path.Combine(collectionFolder.FolderPath, "new name"), true); Assert.That(File.Exists(tc.GetPathToBookFileInRepo("new name")), Is.True); Assert.That(File.Exists(tc.GetPathToBookFileInRepo("old name")), Is.False, "old name was not deleted"); var status = tc.GetLocalStatus("new name"); Assert.That(status.oldName ?? "", Is.Empty, "Should stop tracking previous name once we cleaned it up"); Assert.That(tc.KnownToHaveBeenDeleted("old name"), Is.False); TeamCollectionManager.ForceCurrentUserForTests(null); } } }
public void ConnectToTeamCollection_SetsUpRequiredFiles() { using (var collectionFolder = new TemporaryFolder("FolderTeamCollectionTests2_Collection")) { using (var sharedFolder = new TemporaryFolder("FolderTeamCollectionTests2_Shared")) { var bookFolderName1 = "Some book"; SyncAtStartupTests.MakeFakeBook(collectionFolder.FolderPath, bookFolderName1, "Something"); // BL-9573 tests cases where the book name isn't exactly the same as the folder name var bookFolderName2 = "Some other book"; SyncAtStartupTests.MakeFakeBook(collectionFolder.FolderPath, "Some other name altogether", "Strange book content", bookFolderName2); var settingsFileName = Path.ChangeExtension(Path.GetFileName(collectionFolder.FolderPath), "bloomCollection"); var settingsPath = Path.Combine(collectionFolder.FolderPath, settingsFileName); // As an aside, this is a convenient place to check that a TC manager created when TC settings does not exist // functions and does not have a current collection. var tcManager = new TeamCollectionManager(settingsPath, null, new BookRenamedEvent(), new BookStatusChangeEvent(), null, null); Assert.That(tcManager.CurrentCollection, Is.Null); RobustFile.WriteAllText(settingsPath, "This is a fake settings file"); FolderTeamCollection.CreateTeamCollectionLinkFile(collectionFolder.FolderPath, sharedFolder.FolderPath); var nonBookFolder = Path.Combine(collectionFolder.FolderPath, "Some other folder"); Directory.CreateDirectory(nonBookFolder); tcManager = new TeamCollectionManager(settingsPath, null, new BookRenamedEvent(), new BookStatusChangeEvent(), null, null); var collection = tcManager.CurrentCollection; // sut (collection as FolderTeamCollection)?.SetupTeamCollection(sharedFolder.FolderPath, new NullWebSocketProgress()); Assert.That(collection, Is.Not.Null); var joinCollectionPath = Path.Combine(sharedFolder.FolderPath, "Join this Team Collection.JoinBloomTC"); Assert.That(File.Exists(joinCollectionPath)); var teamCollectionLinkPath = Path.Combine(collectionFolder.FolderPath, TeamCollectionManager.TeamCollectionLinkFileName); Assert.That(File.Exists(teamCollectionLinkPath)); var collectionFileContent = RobustFile.ReadAllText(teamCollectionLinkPath); Assert.That(collectionFileContent, Is.EqualTo(sharedFolder.FolderPath)); var sharedSettingsPath = Path.Combine(collectionFolder.FolderPath, settingsFileName); Assert.That(RobustFile.ReadAllText(sharedSettingsPath), Is.EqualTo("This is a fake settings file")); var bookPath = Path.Combine(sharedFolder.FolderPath, "Books", bookFolderName1 + ".bloom"); Assert.That(File.Exists(bookPath)); var bookPath2 = Path.Combine(sharedFolder.FolderPath, "Books", bookFolderName2 + ".bloom"); Assert.That(File.Exists(bookPath2)); } } }
public void OkToCheckIn_GivesCorrectResults() { using (var collectionFolder = new TemporaryFolder("OkToCheckIn_GivesCorrectResults_Collection")) { using (var repoFolder = new TemporaryFolder("OkToCheckIn_GivesCorrectResults_Shared")) { var mockTcManager = new Mock <ITeamCollectionManager>(); TeamCollectionManager.ForceCurrentUserForTests(""); var tc = new FolderTeamCollection(mockTcManager.Object, collectionFolder.FolderPath, repoFolder.FolderPath); tc.CollectionId = Bloom.TeamCollection.TeamCollection.GenerateCollectionId(); var bookFolderPath = SyncAtStartupTests.MakeFakeBook(collectionFolder.FolderPath, "some name", "book content"); Assert.That(tc.OkToCheckIn("some name"), Is.False, "can't check in new book when not registered"); TeamCollectionManager.ForceCurrentUserForTests("*****@*****.**"); Assert.That(tc.OkToCheckIn("some name"), Is.True, "can check in new book"); tc.PutBook(bookFolderPath, true); tc.AttemptLock("some name"); Assert.That(tc.OkToCheckIn("some name"), Is.True, "can check in unmodified book with normal checkout status"); TeamCollectionManager.ForceCurrentUserForTests(""); Assert.That(tc.OkToCheckIn("some name"), Is.False, "normally permitted checkin is forbidden with no registration"); TeamCollectionManager.ForceCurrentUserForTests("*****@*****.**"); var status = tc.GetStatus("some name"); var altStatus = status.WithChecksum("some random thing"); tc.WriteBookStatus("some name", altStatus); tc.WriteLocalStatus("some name", status); Assert.That(tc.OkToCheckIn("some name"), Is.False, "can't check in, mysteriously modified in repo"); altStatus = status.WithLockedBy(null); tc.WriteBookStatus("some name", altStatus); tc.WriteLocalStatus("some name", status); Assert.That(tc.OkToCheckIn("some name"), Is.True, "special case, repo has lost checkout status, but not locked or modified"); altStatus = status.WithLockedBy("*****@*****.**"); tc.WriteBookStatus("some name", altStatus); tc.WriteLocalStatus("some name", status); Assert.That(tc.OkToCheckIn("some name"), Is.False, "conflicting lock in repo"); TeamCollectionManager.ForceCurrentUserForTests("null"); } } }
public void Setup() { TeamCollectionManager.ForceCurrentUserForTests("*****@*****.**"); _sharedFolder = new TemporaryFolder("TeamCollection_Shared"); _collectionFolder = new TemporaryFolder("TeamCollection_Local"); _tcLog = new TeamCollectionMessageLog(TeamCollectionManager.GetTcLogPathFromLcPath(_collectionFolder.FolderPath)); FolderTeamCollection.CreateTeamCollectionLinkFile(_collectionFolder.FolderPath, _sharedFolder.FolderPath); _mockTcManager = new Mock <ITeamCollectionManager>(); _collection = new FolderTeamCollection(_mockTcManager.Object, _collectionFolder.FolderPath, _sharedFolder.FolderPath, _tcLog); _collection.CollectionId = Bloom.TeamCollection.TeamCollection.GenerateCollectionId(); }
public void ChangeToFileInOther_RaisesRepoCollectionFilesChanged() { using (var collectionFolder = new TemporaryFolder("ChangeToFileInOther_RaisesRepoCollectionFilesChanged")) { using (var repoFolder = new TemporaryFolder("ChangeToFileInOther_RaisesRepoCollectionFilesChanged")) { var mockTcManager = new Mock <ITeamCollectionManager>(); var tc = new TestFolderTeamCollection(mockTcManager.Object, collectionFolder.FolderPath, repoFolder.FolderPath); var otherPath = Path.Combine(collectionFolder.FolderPath, Path.GetFileName(collectionFolder.FolderPath) + ".bloomCollection"); Directory.CreateDirectory(Path.GetDirectoryName(otherPath)); // this test doesn't need this folder except that StartMonitoring does. Directory.CreateDirectory(Path.Combine(repoFolder.FolderPath, "Books")); File.WriteAllText(otherPath, "This is the initial value"); tc.CopyRepoCollectionFilesFromLocal(collectionFolder.FolderPath); var eventWasRaised = false; tc.SetupMonitoringBehavior(); ManualResetEvent collectionChangedRaised = new ManualResetEvent(false); EventHandler <EventArgs> monitorFunction = (sender, args) => { eventWasRaised = true; collectionChangedRaised.Set(); }; tc.RepoCollectionFilesChanged += monitorFunction; // sut (at least, triggers it and waits for it) Thread.Sleep(10); var otherRepoPath = FolderTeamCollection.GetRepoProjectFilesZipPath(repoFolder.FolderPath); RobustFile.WriteAllText(otherRepoPath, @"This is changed"); // no, not a zip at all var waitSucceeded = collectionChangedRaised.WaitOne(1000); // To avoid messing up other tests, clean up before asserting. tc.RepoCollectionFilesChanged -= monitorFunction; tc.StopMonitoring(); Assert.That(eventWasRaised, Is.True, "event was not raised"); } } }
public void ConnectToTeamCollection(string repoFolderParentPath, string collectionId) { var repoFolderPath = PlannedRepoFolderPath(repoFolderParentPath); Directory.CreateDirectory(repoFolderPath); // The creator of a TC is its first and, for now, usually only administrator. // (Currently there is no way to change this except to hand-edit the file.) Settings.Administrators = new[] { CurrentUser }; Settings.Save(); var newTc = new FolderTeamCollection(this, _localCollectionFolder, repoFolderPath, bookCollectionHolder: _bookCollectionHolder); newTc.CollectionId = collectionId; newTc.SocketServer = SocketServer; newTc.TCManager = this; newTc.SetupTeamCollectionWithProgressDialog(repoFolderPath); CurrentCollection = newTc; CurrentCollectionEvenIfDisconnected = newTc; }
public void OneTimeSetup() { _repoFolder = new TemporaryFolder("FolderTeamCollectionTests_Repo"); _collectionFolder = new TemporaryFolder("FolderTeamCollectionTests_Local"); FolderTeamCollection.CreateTeamCollectionLinkFile(_collectionFolder.FolderPath, _repoFolder.FolderPath); _mockTcManager = new Mock <ITeamCollectionManager>(); _collection = new TestFolderTeamCollection(_mockTcManager.Object, _collectionFolder.FolderPath, _repoFolder.FolderPath); // Some monitoring tests now require this to exist Directory.CreateDirectory(Path.Combine(_repoFolder.FolderPath, "Other")); // Make some books and check them in. Individual tests verify the results. // This book has an additional file, including a subfolder, to ensure they get // saved also. var bookFolderPath = Path.Combine(_collectionFolder.FolderPath, "My book"); Directory.CreateDirectory(bookFolderPath); var bookPath = Path.Combine(bookFolderPath, "My book.htm"); RobustFile.WriteAllText(bookPath, "This is just a dummy"); var cssPath = Path.Combine(bookFolderPath, "BasicLayout.css"); RobustFile.WriteAllText(cssPath, "This is another dummy"); var audioDirectory = Path.Combine(bookFolderPath, "audio"); Directory.CreateDirectory(audioDirectory); var mp3Path = Path.Combine(audioDirectory, "rubbish.mp3"); RobustFile.WriteAllText(mp3Path, "Fake mp3"); var secondBookFolderPath = Path.Combine(_collectionFolder.FolderPath, kAnotherBook); Directory.CreateDirectory(secondBookFolderPath); var anotherBookPath = Path.Combine(secondBookFolderPath, kAnotherBook + ".htm"); RobustFile.WriteAllText(anotherBookPath, "This is just a dummy for another book"); _myBookStatus = _collection.PutBook(bookFolderPath); _anotherBookStatus = _collection.PutBook(secondBookFolderPath); // Also put the book (twice!) into lost and found _collection.PutBook(secondBookFolderPath, inLostAndFound: true); _collection.PutBook(secondBookFolderPath, inLostAndFound: true); }
public void FilesToMonitorForCollection_NonStandardCollectionFileName_FindsIt() { using (var collectionFolder = new TemporaryFolder("SyncLocalAndRepoCollectionFiles_SyncsInRightDirection_Collection")) { using (var repoFolder = new TemporaryFolder("SyncLocalAndRepoCollectionFiles_SyncsInRightDirection_Shared")) { var mockTcManager = new Mock <ITeamCollectionManager>(); var tc = new FolderTeamCollection(mockTcManager.Object, collectionFolder.FolderPath, repoFolder.FolderPath); tc.CollectionId = Bloom.TeamCollection.TeamCollection.GenerateCollectionId(); var bcPath = Path.Combine(collectionFolder.FolderPath, "mybooks.bloomCollection"); File.WriteAllText(bcPath, "something"); var files = tc.FilesToMonitorForCollection(); Assert.That(files, Contains.Item(bcPath)); } } }
public void OneTimeSetup() { _repoFolder = new TemporaryFolder("SyncAtStartup_Repo"); _collectionFolder = new TemporaryFolder("SyncAtStartup_Local"); FolderTeamCollection.CreateTeamCollectionLinkFile(_collectionFolder.FolderPath, _repoFolder.FolderPath); _mockTcManager = new Mock <ITeamCollectionManager>(); _tcLog = new TeamCollectionMessageLog(TeamCollectionManager.GetTcLogPathFromLcPath(_collectionFolder.FolderPath)); _collection = new FolderTeamCollection(_mockTcManager.Object, _collectionFolder.FolderPath, _repoFolder.FolderPath, tcLog: _tcLog); _collection.CollectionId = Bloom.TeamCollection.TeamCollection.GenerateCollectionId(); TeamCollectionManager.ForceCurrentUserForTests("*****@*****.**"); // Simulate a book that was once shared, but has been deleted from the repo folder (has a tombstone). MakeBook("Should be deleted", "This should be deleted as it has local status but is not shared", true); var bookFolderPath = Path.Combine(_collectionFolder.FolderPath, "Should be deleted"); _collection.DeleteBookFromRepo(bookFolderPath); // Simulate a book that was once shared, but has been deleted from the repo folder. But there is no tombstone. // (Despite the name, it is only converted to a new local in the default case. When we do a First Time Join, // it just gets copied into the repo.) MakeBook("Should be converted to new local", "This should become a new local (no status) book as it has local status but is not in the repo", true); var delPath = Path.Combine(_repoFolder.FolderPath, "Books", "Should be converted to new local.bloom"); RobustFile.Delete(delPath); // Simulate a book newly created locally. Not in repo, but should not be deleted. MakeBook("A book", "This should survive as it has no local status", false); // By the way, like most new books, it got renamed early in life...twice SimulateRename(_collection, "A book", "An early name"); SimulateRename(_collection, "An early name", "New book"); // Simulate a book that needs nothing done to it. It's the same locally and on the repo. MakeBook("Keep me", "This needs nothing done to it"); // Simulate a book that is checked out locally to the current user, but the file has // been deleted on the repo. MakeBook("Keep me too", "This also needs nothing done", false); _collection.WriteLocalStatus("Keep me too", new BookStatus().WithLockedBy("*****@*****.**")); // Simlulate a book that is only in the team repo MakeBook("Add me", "Fetch to local"); var delPathAddMe = Path.Combine(_collectionFolder.FolderPath, "Add me"); SIL.IO.RobustIO.DeleteDirectoryAndContents(delPathAddMe); // Simulate a book that was checked in, then checked out again and renamed, // but not yet checked in. Both "A renamed book" folder and content and "An old name.bloom" // should survive. (Except for an obscure reason when joining a TC...see comment in the test.) MakeBook("An old name", "Should be kept in both places with different names"); _collection.AttemptLock("An old name", "*****@*****.**"); SimulateRename(_collection, "An old name", "an intermediate name"); SimulateRename(_collection, "an intermediate name", "A renamed book"); // Simulate a book that is not checked out locally and has been modified elsewhere MakeBook("Update me", "Needs to be become this locally"); UpdateLocalBook("Update me", "This is supposed to be an older value, not edited locally"); // Simulate a book that is checked out locally but not in the repo, and where the saved local // checksum equals the repo checksum, and it is not checked out in the repo. This would // typically indicate that someone remote forced a checkout, perhaps while this user was // offline, but checked in again without making changes. // Also pretend it has been modified locally. // Test result: collection is updated to indicate the local checkout. Local changes are not lost. MakeBook("Check me out", "Local and remote checksums correspond to this"); UpdateLocalBook("Check me out", "This is supposed to be a newer value from local editing", false); var oldLocalStatus = _collection.GetLocalStatus("Check me out"); var newLocalStatus = oldLocalStatus.WithLockedBy(Bloom.TeamCollection.TeamCollectionManager.CurrentUser); _checkMeOutOriginalChecksum = oldLocalStatus.checksum; _collection.WriteLocalStatus("Check me out", newLocalStatus); // Simulate a book that appears newly-created locally (no local status) but is also in the // repo. This would indicate two people coincidentally creating a book with the same name. // Test result: the local book should get renamed (both folder and htm). // When merging while joining a new TC, this case is treated as a conflict and the // local book is moved to Lost and Found. MakeBook("Rename local", "This content is on the server"); _collection.AttemptLock("Rename local", "*****@*****.**"); UpdateLocalBook("Rename local", "This is a new book created independently"); var statusFilePath = Bloom.TeamCollection.TeamCollection.GetStatusFilePath("Rename local", _collectionFolder.FolderPath); RobustFile.Delete(statusFilePath); // Simulate a book that is checked out locally but also checked out, to a different user // or machine, on the repo. This would indicate some sort of manual intervention, perhaps // while this user was long offline. The book has not been modified locally, but the local // status is out of date. // Test result: local status is updated to reflect the remote checkout, book content updated to repo. MakeBook("Update and undo checkout", "This content is everywhere"); _collection.AttemptLock("Update and undo checkout", "*****@*****.**"); _collection.WriteLocalStatus("Update and undo checkout", _collection.GetStatus("Update and undo checkout").WithLockedBy(Bloom.TeamCollection.TeamCollectionManager.CurrentUser)); // Simulate a book that is checked out locally and not on the server, but the repo and (old) // local checksums are different. The book has not been edited locally. // Test result: book is updated to match repo. Local and remote status should match...review: which wins? MakeBook("Update and checkout", "This content is on the server"); UpdateLocalBook("Update and checkout", "This simulates older content changed remotely but not locally"); _collection.WriteLocalStatus("Update and checkout", _collection.GetLocalStatus("Update and checkout").WithLockedBy(Bloom.TeamCollection.TeamCollectionManager.CurrentUser)); // Simulate a book that is checked out and modified locally, but has also been modified // remotely. // Test result: current local state is saved in lost-and-found. Repo version of book and state // copied to local. Warning to user. MakeBook("Update content and status and warn", "This simulates new content on server"); _collection.AttemptLock("Update content and status and warn", "*****@*****.**"); UpdateLocalBook("Update content and status and warn", "This is supposed to be the newest value from local editing"); var newStatus = _collection.GetStatus("Update content and status and warn").WithLockedBy(Bloom.TeamCollection.TeamCollectionManager.CurrentUser) .WithChecksum("different from either"); _collection.WriteLocalStatus("Update content and status and warn", newStatus); // Simulate a book that is checked out and modified locally, but is also checked out by another // user or machine in the repo. It has not (yet) been modified remotely. // Test result: current local state is saved in lost-and-found. Repo version of book and state // copied to local. Warning to user. MakeBook("Update content and status and warn2", "This simulates new content on server"); _collection.AttemptLock("Update content and status and warn2", "*****@*****.**"); UpdateLocalBook("Update content and status and warn2", "This is supposed to be the newest value from local editing", false); newStatus = _collection.GetStatus("Update content and status and warn2").WithLockedBy(Bloom.TeamCollection.TeamCollectionManager.CurrentUser); _collection.WriteLocalStatus("Update content and status and warn2", newStatus); // Simulate a book which has no local status, but for which the computed checksum matches // the repo one. This could happen if a user obtained the same book independently, // or during initial merging of a local and team collection, where much of the material // was previously duplicated. // Test result: status is copied to local MakeBook("copy status", "Same content in both places"); _collection.AttemptLock("copy status", "*****@*****.**"); statusFilePath = Bloom.TeamCollection.TeamCollection.GetStatusFilePath("copy status", _collectionFolder.FolderPath); RobustFile.Delete(statusFilePath); // Simulate a book that was copied from another TC, using File Explorer. // It therefore has a book.status file, but with a different guid. // Test result: it should survive, and on a new collection sync get copied into the repo var copiedEx = "copied with Explorer"; MakeBook(copiedEx, "This content is only local", false); _collection.WriteLocalStatus(copiedEx, new BookStatus(), collectionId: Bloom.TeamCollection.TeamCollection.GenerateCollectionId()); // Simulate a book that appeared in DropBox when their software found a conflict. // It should NOT be copied locally, but instead moved to Lost and Found, with a report. MakeBook(kConflictName, "This content is only on the repo, apart from conflicting copies"); var conflictFolderPath = Path.Combine(_collectionFolder.FolderPath, kConflictName); SIL.IO.RobustIO.DeleteDirectoryAndContents(conflictFolderPath); _collection.WriteLocalStatus(copiedEx, new BookStatus(), collectionId: Bloom.TeamCollection.TeamCollection.GenerateCollectionId()); // Simulate a corrupt zip file, only in the repo File.WriteAllText(Path.Combine(_repoFolder.FolderPath, "Books", "new corrupt book.bloom"), "This is not a valid zip!"); // Simulate a corrupt zip file that corresponds to a local book. var badZip = "has a bad zip in repo"; MakeBook(badZip, "This book seems to be in both places, but the repo is corrupt"); File.WriteAllText(Path.Combine(_repoFolder.FolderPath, "Books", badZip + ".bloom"), "This is also not a valid zip!"); // Simulate a book that was renamed remotely. That is, there's a local book Old Name, with local status, // and there's no repo book by that name, but there's a repo book New Name (and no such local book). // The book's meta.json indicates they are the same book. // We'll initially make both, with the new name and new content. MakeBook(kNewNameForRemoteRename, "This is the new book content after remote editing and rename"); var oldFolder = Path.Combine(_collectionFolder.FolderPath, kBookRenamedRemotely); var newFolder = Path.Combine(_collectionFolder.FolderPath, kNewNameForRemoteRename); RobustIO.MoveDirectory(newFolder, oldFolder); // made at new path, simulate still at old. var oldPath = Path.Combine(_collectionFolder.FolderPath, kBookRenamedRemotely, kBookRenamedRemotely + ".htm"); // simulate old book name and content RobustFile.WriteAllText(oldPath, "This is the simulated original book content"); RobustFile.Delete(Path.Combine(_collectionFolder.FolderPath, kBookRenamedRemotely, kNewNameForRemoteRename + ".htm")); // get rid of the 'new' content // Simulate a book that is in the repo, where there is a local book that has no status, a different name, // and the same ID. This might indicate (a) that it was renamed by someone else after this user's pre-TC // copy of the collection diverged; (b) that it was renamed by this user after the divergence; // (c) that they were independently copied from some common-ID source. // We will treat this as a conflict, moving the local version to lost and found, even on a first time join. MakeBook(kRepoNameForIdConflict, "This is the repo version of a book that has a no-status local book with the same ID."); // Move the local version to a new folder var oldFolder2 = Path.Combine(_collectionFolder.FolderPath, kRepoNameForIdConflict); var newFolder2 = Path.Combine(_collectionFolder.FolderPath, kLocalNameForIdConflict); RobustIO.MoveDirectory(oldFolder2, newFolder2); var localStatusPath = Bloom.TeamCollection.TeamCollection.GetStatusFilePath(kLocalNameForIdConflict, _collectionFolder.FolderPath); RobustFile.Delete(localStatusPath); // Make a couple of folders that are legitimately present, but not books. var allowedWords = Path.Combine(_collectionFolder.FolderPath, "Allowed Words"); Directory.CreateDirectory(allowedWords); File.WriteAllText(Path.Combine(allowedWords, "some sample.txt"), "This a fake word list"); var sampleTexts = Path.Combine(_collectionFolder.FolderPath, "Sample Texts"); Directory.CreateDirectory(sampleTexts); File.WriteAllText(Path.Combine(sampleTexts, "a sample.txt"), "This a fake sample text"); _progressSpy = new ProgressSpy(); // sut for the whole suite! Assert.That(_collection.SyncAtStartup(_progressSpy, FirstTimeJoin()), Is.True); }
public TeamCollectionManager(string localCollectionPath, BloomWebSocketServer webSocketServer, BookRenamedEvent bookRenamedEvent, BookStatusChangeEvent bookStatusChangeEvent, BookSelection bookSelection, CollectionClosing collectionClosingEvent, BookCollectionHolder bookCollectionHolder) { _webSocketServer = webSocketServer; _bookStatusChangeEvent = bookStatusChangeEvent; _localCollectionFolder = Path.GetDirectoryName(localCollectionPath); _bookCollectionHolder = bookCollectionHolder; BookSelection = bookSelection; collectionClosingEvent?.Subscribe((x) => { // When closing the collection...especially if we're restarting due to // changed settings!...we need to save any settings changes to the repo. // In such cases we can't safely wait for the change watcher to write things, // because (a) if we're shutting down for good, we just might not detect the // change before everything shuts down; and (b) if we're reopening the collection, // we might overwrite the change with current collection settings before we // save the new ones. if (CurrentCollection != null) { CurrentCollection.SyncLocalAndRepoCollectionFiles(false); } else if (Settings.HaveEnterpriseFeatures && CurrentCollectionEvenIfDisconnected != null && CurrentCollectionEvenIfDisconnected is DisconnectedTeamCollection disconnectedTC && disconnectedTC.DisconnectedBecauseNoEnterprise) { // We were disconnected because of Enterprise being off, but now the user has // turned Enterprise on again. We really need to save that, even though we usually don't // save settings changes when disconnected. Otherwise, restarting will restore the // no-enterprise state, and we will be stuck. // Note: We don't need to check for admin privileges here. If the user isn't an admin, // he could not have made any changes to settings, including turning on enterprise. var tempCollectionLinkPath = GetTcLinkPathFromLcPath(_localCollectionFolder); if (RobustFile.Exists(tempCollectionLinkPath)) { try { var repoFolderPath = RepoFolderPathFromLinkPath(tempCollectionLinkPath); var tempCollection = new FolderTeamCollection(this, _localCollectionFolder, repoFolderPath, bookCollectionHolder: _bookCollectionHolder); var problemWithConnection = tempCollection.CheckConnection(); if (problemWithConnection == null) { tempCollection.SyncLocalAndRepoCollectionFiles(false); } else { NonFatalProblem.Report(ModalIf.All, PassiveIf.All, "Bloom could not save your settings to the Team Collection: " + problemWithConnection.TextForDisplay, null, null, true); } } catch (Exception ex) { NonFatalProblem.Report(ModalIf.All, PassiveIf.All, "Bloom could not save your settings to the Team Collection", null, ex, true); } } // What if there's NOT a TC link file? Then it would be pathological to have a CurrentCollectionEvenIfDisconnected. // It's no longer a TC, so we don't need to save the settings to the TC. For now I'm just not going to do anything. } }); bookRenamedEvent.Subscribe(pair => { CurrentCollectionEvenIfDisconnected?.HandleBookRename(Path.GetFileName(pair.Key), Path.GetFileName(pair.Value)); }); var impersonatePath = Path.Combine(_localCollectionFolder, "impersonate.txt"); if (RobustFile.Exists(impersonatePath)) { var lines = RobustFile.ReadAllLines(impersonatePath); _overrideCurrentUser = lines.FirstOrDefault(); if (lines.Length > 1) { _overrideMachineName = lines[1]; } if (lines.Length > 2) { _overrideCurrentUserFirstName = lines[2]; } if (lines.Length > 3) { _overrideCurrentUserSurname = lines[3]; } } var localCollectionLinkPath = GetTcLinkPathFromLcPath(_localCollectionFolder); if (RobustFile.Exists(localCollectionLinkPath)) { try { var repoFolderPath = RepoFolderPathFromLinkPath(localCollectionLinkPath); CurrentCollection = new FolderTeamCollection(this, _localCollectionFolder, repoFolderPath, bookCollectionHolder: _bookCollectionHolder); // will be replaced if CheckConnection fails // BL-10704: We set this to the CurrentCollection BEFORE checking the connection, // so that there will be a valid MessageLog if we need it during CheckConnection(). // If CheckConnection() fails, it will reset this to a DisconnectedTeamCollection. CurrentCollectionEvenIfDisconnected = CurrentCollection; if (CheckConnection()) { CurrentCollection.SocketServer = SocketServer; CurrentCollection.TCManager = this; // Later, we will sync everything else, but we want the current collection settings before // we create the CollectionSettings object. if (ForceNextSyncToLocal) { ForceNextSyncToLocal = false; CurrentCollection.CopyRepoCollectionFilesToLocal(_localCollectionFolder); } else { CurrentCollection.SyncLocalAndRepoCollectionFiles(); } } // else CheckConnection has set up a DisconnectedRepo if that is relevant. } catch (Exception ex) { NonFatalProblem.Report(ModalIf.All, PassiveIf.All, "Bloom found Team Collection settings but could not process them", null, ex, true); CurrentCollection = null; CurrentCollectionEvenIfDisconnected = null; } } }
public void SyncLocalAndRepoCollectionFiles_SyncsInRightDirection() { using (var collectionFolder = new TemporaryFolder("SyncLocalAndRepoCollectionFiles_SyncsInRightDirection_Collection")) { using (var repoFolder = new TemporaryFolder("SyncLocalAndRepoCollectionFiles_SyncsInRightDirection_Shared")) { var settingsFileName = Path.ChangeExtension(Path.GetFileName(collectionFolder.FolderPath), "bloomCollection"); var settingsPath = Path.Combine(collectionFolder.FolderPath, settingsFileName); var tcManager = new TeamCollectionManager(settingsPath, null, new BookRenamedEvent(), new BookStatusChangeEvent(), null, null); var tc = new FolderTeamCollection(tcManager, collectionFolder.FolderPath, repoFolder.FolderPath); tc.CollectionId = Bloom.TeamCollection.TeamCollection.GenerateCollectionId(); var bloomCollectionPath = Bloom.TeamCollection.TeamCollection.CollectionPath(collectionFolder.FolderPath); Assert.That(tc.LocalCollectionFilesRecordedSyncTime, Is.EqualTo(DateTime.MinValue)); File.WriteAllText(bloomCollectionPath, "This is a fake collection file"); var collectionStylesPath = Path.Combine(collectionFolder.FolderPath, "customCollectionStyles.css"); RobustFile.WriteAllText(collectionStylesPath, "This is the collection styles"); // SUT 1: nothing in repo, no sync time file. Copies to repo. tc.SyncLocalAndRepoCollectionFiles(); var localWriteTime1 = tc.LocalCollectionFilesRecordedSyncTime(); Assert.That(localWriteTime1, Is.LessThanOrEqualTo(DateTime.Now)); Assert.That(localWriteTime1, Is.GreaterThan(DateTime.Now.Subtract(new TimeSpan(0, 0, 5, 0)))); var otherFilesPath = FolderTeamCollection.GetRepoProjectFilesZipPath(repoFolder.FolderPath); Assert.That(File.Exists(otherFilesPath)); var anotherPlace = Path.Combine(repoFolder.FolderPath, "anotherPlace.zip"); RobustFile.Copy(otherFilesPath, anotherPlace); var repoWriteTime1 = new FileInfo(otherFilesPath).LastWriteTime; var collectionWriteTime1 = new FileInfo(bloomCollectionPath).LastWriteTime; // SUT 2: nothing has changed. But it's a startup, so sync still happens to local. tc.SyncLocalAndRepoCollectionFiles(); var localWriteTime2 = tc.LocalCollectionFilesRecordedSyncTime(); Assert.That(localWriteTime2, Is.GreaterThanOrEqualTo(localWriteTime1)); Assert.That(new FileInfo(otherFilesPath).LastWriteTime, Is.EqualTo(repoWriteTime1)); Assert.That(new FileInfo(bloomCollectionPath).LastWriteTime, Is.GreaterThanOrEqualTo(collectionWriteTime1)); File.WriteAllText(bloomCollectionPath, "This is a modified fake collection file"); var collectionWriteTime2 = new FileInfo(bloomCollectionPath).LastWriteTime; // SUT 3: local change copied to repo (only when not at startup) tc.SyncLocalAndRepoCollectionFiles(false); var localWriteTime3 = tc.LocalCollectionFilesRecordedSyncTime(); Assert.That(localWriteTime3, Is.GreaterThan(localWriteTime1), "localWriteTime3 should be greater than localWriteTime1"); var repoWriteTime2 = new FileInfo(otherFilesPath).LastWriteTime; Assert.That(repoWriteTime2, Is.GreaterThan(repoWriteTime1), "repoWriteTime2 should be greater than repoWriteTime1"); // not modified by sync Assert.That(new FileInfo(bloomCollectionPath).LastWriteTime, Is.EqualTo(collectionWriteTime2)); File.WriteAllText(bloomCollectionPath, "This is a further modified fake collection file"); var collectionWriteTime3 = new FileInfo(bloomCollectionPath).LastWriteTime; var version2Path = Path.Combine(repoFolder.FolderPath, "version2.zip"); RobustFile.Copy(otherFilesPath, version2Path); // modify the remote version by copying the old one back. Thread.Sleep(10); RobustFile.Copy(anotherPlace, otherFilesPath, true); var repoWriteTime3 = new FileInfo(otherFilesPath).LastWriteTime; Assert.That(repoWriteTime3, Is.GreaterThan(collectionWriteTime3), "repo file written after local collection file [sanity check]"); // SUT 4: both changed: repo wins tc.SyncLocalAndRepoCollectionFiles(); var localWriteTime4 = tc.LocalCollectionFilesRecordedSyncTime(); Assert.That(localWriteTime4, Is.GreaterThan(localWriteTime3), "localWriteTime4 should be greater than localWriteTime3"); var repoWriteTime4 = new FileInfo(otherFilesPath).LastWriteTime; Assert.That(repoWriteTime4, Is.EqualTo(repoWriteTime3)); // not modified by sync Assert.That(new FileInfo(bloomCollectionPath).LastWriteTime, Is.GreaterThan(collectionWriteTime3), "bloomCollection LastWriteTime should be greater than collectionWriteTime3"); // We got the original back. Assert.That(File.ReadAllText(bloomCollectionPath), Is.EqualTo("This is a fake collection file")); Thread.Sleep(10); var allowedWords = Path.Combine(collectionFolder.FolderPath, "Allowed Words"); Directory.CreateDirectory(allowedWords); File.WriteAllText(Path.Combine(allowedWords, "file1.txt"), "fake word list"); // SUT5: local allowed words added tc.SyncLocalAndRepoCollectionFiles(false); var localWriteTime5 = tc.LocalCollectionFilesRecordedSyncTime(); Assert.That(localWriteTime5, Is.GreaterThan(localWriteTime4), "localWriteTime5 should be greater than localWriteTime4"); var repoWriteTime5 = new FileInfo(otherFilesPath).LastWriteTime; Assert.That(repoWriteTime5, Is.GreaterThan(repoWriteTime4), "repoWriteTime5 should be greater than repoWriteTime4"); Thread.Sleep(5); var sampleTexts = Path.Combine(collectionFolder.FolderPath, "Sample Texts"); Directory.CreateDirectory(sampleTexts); File.WriteAllText(Path.Combine(allowedWords, "sample1.txt"), "fake sample list"); // SUT6: local sample texts added tc.SyncLocalAndRepoCollectionFiles(false); var localWriteTime6 = tc.LocalCollectionFilesRecordedSyncTime(); Assert.That(localWriteTime6, Is.GreaterThan(localWriteTime5), "localWriteTime6 should be greater than localWriteTime5"); var repoWriteTime6 = new FileInfo(otherFilesPath).LastWriteTime; Assert.That(repoWriteTime6, Is.GreaterThan(repoWriteTime5), "repoWriteTime6 should be greater than repoWriteTime5"); Thread.Sleep(10); File.WriteAllText(Path.Combine(allowedWords, "sample1.txt"), "fake sample list"); // SUT7: local file write time modified, but not actually changed. Want the sync time to // update, but NOT to write the remote file. tc.SyncLocalAndRepoCollectionFiles(false); var localWriteTime7 = tc.LocalCollectionFilesRecordedSyncTime(); Assert.That(localWriteTime7, Is.GreaterThan(localWriteTime6), "localWriteTime7 should be greater than localWriteTime6"); var repoWriteTime7 = new FileInfo(otherFilesPath).LastWriteTime; Assert.That(repoWriteTime7, Is.EqualTo(repoWriteTime6)); tc._haveShownRemoteSettingsChangeWarning = false; File.WriteAllText(bloomCollectionPath, "This is a modified fake collection file, for SUT 8"); var collectionWriteTimeBeforeSut8 = new FileInfo(bloomCollectionPath).LastWriteTime; var localWriteTimeBeforeSut8 = tc.LocalCollectionFilesRecordedSyncTime(); var repoWriteTimeBeforeSut8 = new FileInfo(otherFilesPath).LastWriteTime; // SUT 8: local change copied to repo on idle tc.SyncLocalAndRepoCollectionFiles(false); Assert.That(tc._haveShownRemoteSettingsChangeWarning, Is.False, "user should not have been warned"); var localWriteTimeAfterSut8 = tc.LocalCollectionFilesRecordedSyncTime(); Assert.That(localWriteTimeAfterSut8, Is.GreaterThan(localWriteTimeBeforeSut8), "localWriteTime should increase copying on idle"); var repoWriteTimeAfterSut8 = new FileInfo(otherFilesPath).LastWriteTime; Assert.That(repoWriteTimeAfterSut8, Is.GreaterThan(repoWriteTimeBeforeSut8), "repoWriteTime should increase copying on idle"); // not modified by sync Assert.That(new FileInfo(bloomCollectionPath).LastWriteTime, Is.EqualTo(collectionWriteTimeBeforeSut8)); // modify the remote version by copying version2 back. Thread.Sleep(10); var repoWriteTimeBeforeSut9Copy = new FileInfo(otherFilesPath).LastWriteTime; RobustFile.Copy(version2Path, otherFilesPath, true); var collectionWriteTimeBeforeSut9 = new FileInfo(bloomCollectionPath).LastWriteTime; var repoWriteTimeBeforeSut9 = new FileInfo(otherFilesPath).LastWriteTime; Assert.That(repoWriteTimeBeforeSut9, Is.GreaterThan(repoWriteTimeBeforeSut9Copy), "repo file written after local collection file [sanity check]"); tc._haveShownRemoteSettingsChangeWarning = false; // SUT9: repo modified, doing check on idle. No changes or warning. tc.SyncLocalAndRepoCollectionFiles(false); Assert.That(tc._haveShownRemoteSettingsChangeWarning, Is.False, "user should not have been warned"); var collectionWriteTimeAfterSut9 = new FileInfo(bloomCollectionPath).LastWriteTime; Assert.That(collectionWriteTimeAfterSut9, Is.EqualTo(collectionWriteTimeBeforeSut9), "local settings should not have been modified"); File.WriteAllText(bloomCollectionPath, "This is a modified fake collection file, for SUT 10"); var collectionWriteTimeBeforeSut10 = new FileInfo(bloomCollectionPath).LastWriteTime; var localWriteTimeBeforeSut10 = tc.LocalCollectionFilesRecordedSyncTime(); var repoWriteTimeBeforeSut10 = new FileInfo(otherFilesPath).LastWriteTime; // SUT10: both modified, doing check on idle. No changes. User warned. using (var nfes = new ErrorReport.NonFatalErrorReportExpected()) { tc.SyncLocalAndRepoCollectionFiles(false); } Assert.That(tc._haveShownRemoteSettingsChangeWarning, Is.True, "user should have been warned"); var localWriteTimeAfterSut10 = tc.LocalCollectionFilesRecordedSyncTime(); Assert.That(localWriteTimeAfterSut10, Is.EqualTo(localWriteTimeBeforeSut10), "localWriteTime should not be changed by idle sync where both changed"); var repoWriteTimeAfterSut10 = new FileInfo(otherFilesPath).LastWriteTime; Assert.That(repoWriteTimeAfterSut10, Is.EqualTo(repoWriteTimeBeforeSut10), "repo should not be modified by idle sync where both changed"); // not modified by sync Assert.That(new FileInfo(bloomCollectionPath).LastWriteTime, Is.EqualTo(collectionWriteTimeBeforeSut10), "bloomCollection LastWriteTime should not be changed by idle sync both changed"); // Get everything back in sync tc.SyncLocalAndRepoCollectionFiles(); var localWriteTimeBeforeSut11 = tc.LocalCollectionFilesRecordedSyncTime(); var repoWriteTimeBeforeSut11 = new FileInfo(otherFilesPath).LastWriteTime; RobustFile.WriteAllText(collectionStylesPath, "This is the modified collection styles"); // SUT11: custom collection styles modified while Bloom was not running. Copied to repo. tc.SyncLocalAndRepoCollectionFiles(); var repoWriteTimeAfterSut11 = new FileInfo(otherFilesPath).LastWriteTime; Assert.That(repoWriteTimeAfterSut11, Is.GreaterThanOrEqualTo(repoWriteTimeBeforeSut11)); var localWriteTimeAfterSut11 = tc.LocalCollectionFilesRecordedSyncTime(); // We will update the sync time even though the write is the other way. Assert.That(localWriteTimeAfterSut11, Is.GreaterThan(localWriteTimeBeforeSut11)); Assert.That(File.ReadAllText(collectionStylesPath), Is.EqualTo("This is the modified collection styles")); } } }