public void HandleNewBook_AddsMessage_IffReallyNew(bool reallyNew) { using (var collectionFolder = new TemporaryFolder("HandleNewBook_NewBook_AddsMessage_Collection")) { using (var repoFolder = new TemporaryFolder("HandleNewBook_NewBook_AddsMessage_Shared")) { var bookFolderName1 = "New book"; var localBookFolderPath = SyncAtStartupTests.MakeFakeBook(collectionFolder.FolderPath, bookFolderName1, "Something"); var mockTcManager = new Mock <ITeamCollectionManager>(); var tcLog = new TeamCollectionMessageLog(TeamCollectionManager.GetTcLogPathFromLcPath(collectionFolder.FolderPath)); var tc = new TestFolderTeamCollection(mockTcManager.Object, collectionFolder.FolderPath, repoFolder.FolderPath, tcLog); tc.PutBook(localBookFolderPath); if (reallyNew) { SIL.IO.RobustIO.DeleteDirectory(localBookFolderPath, true); } tc.HandleNewBook(new NewBookEventArgs() { BookFileName = "New book.bloom" }); if (reallyNew) { var msg = tcLog.Messages[0]; Assert.That(msg.RawEnglishMessageTemplate, Is.EqualTo("A new book called '{0}' was added by a teammate.")); } else { Assert.That(tcLog.Messages.Count, Is.EqualTo(0)); } } } }
public void AnyBooksCheckedOutHereByCurrentUser_TrueOnlyForRealCheckouts() { using (var collectionFolder = new TemporaryFolder("AnyBooksCheckedOutHereByCurrentUser_TrueOnlyForRealCheckouts")) { using (var repoFolder = new TemporaryFolder("AnyBooksCheckedOutHereByCurrentUser_TrueOnlyForRealCheckouts")) { TeamCollectionManager.ForceCurrentUserForTests("*****@*****.**"); var bookFolderName1 = "A very nice book book"; var localBookFolderPath = SyncAtStartupTests.MakeFakeBook(collectionFolder.FolderPath, bookFolderName1, "Something"); var mockTcManager = new Mock <ITeamCollectionManager>(); var tcLog = new TeamCollectionMessageLog(TeamCollectionManager.GetTcLogPathFromLcPath(collectionFolder.FolderPath)); var tc = new TestFolderTeamCollection(mockTcManager.Object, collectionFolder.FolderPath, repoFolder.FolderPath, tcLog); SyncAtStartupTests.MakeFakeBook(collectionFolder.FolderPath, "Another nice book", "Something"); Assert.That(tc.AnyBooksCheckedOutHereByCurrentUser, Is.False); // both currently local-only tc.PutBook(localBookFolderPath); Assert.That(tc.AnyBooksCheckedOutHereByCurrentUser, Is.False); // one local-only, one checked in tc.AttemptLock(bookFolderName1, TeamCollectionManager.CurrentUser); Assert.That(tc.AnyBooksCheckedOutHereByCurrentUser, Is.True); // one local-only, one checked out tc.PutBook(localBookFolderPath, checkin: true); tc.AttemptLock(bookFolderName1, "someoneElse.somewhere.org"); Assert.That(tc.AnyBooksCheckedOutHereByCurrentUser, Is.False); // one local-only, one checked out but to someone else. } } }
public void HandleNewBook_RenamedBook_AddsRenameMessage() { using (var collectionFolder = new TemporaryFolder("HandleNewBook_RenamedBook_AddsRenameMessage_Collection")) { using (var repoFolder = new TemporaryFolder("HandleNewBook_RenamedBook_AddsRenameMessage_Shared")) { var bookFolderName1 = "Renamed book"; var localBookFolderPath = SyncAtStartupTests.MakeFakeBook(collectionFolder.FolderPath, bookFolderName1, "Something"); var mockTcManager = new Mock <ITeamCollectionManager>(); var tcLog = new TeamCollectionMessageLog(TeamCollectionManager.GetTcLogPathFromLcPath(collectionFolder.FolderPath)); var tc = new TestFolderTeamCollection(mockTcManager.Object, collectionFolder.FolderPath, repoFolder.FolderPath, tcLog); tc.PutBook(localBookFolderPath); SIL.IO.RobustIO.MoveDirectory(localBookFolderPath, Path.Combine(collectionFolder.FolderPath, "old name")); // We could rename the book file too, but it doesn't matter for the current SUT tc.HandleNewBook(new NewBookEventArgs() { BookFileName = "Renamed book.bloom" }); var msg = tcLog.Messages[0]; Assert.That(msg.RawEnglishMessageTemplate, Is.EqualTo("The book \"{0}\" has been renamed to \"{1}\" by a teammate.")); Assert.That(msg.Param0, Is.EqualTo("old name")); Assert.That(msg.Param1, Is.EqualTo("Renamed book")); } } }
public CollectionModel(string pathToCollection, CollectionSettings collectionSettings, BookSelection bookSelection, SourceCollectionsList sourceCollectionsList, BookCollection.Factory bookCollectionFactory, EditBookCommand editBookCommand, CreateFromSourceBookCommand createFromSourceBookCommand, BookServer bookServer, CurrentEditableCollectionSelection currentEditableCollectionSelection, BookThumbNailer thumbNailer, TeamCollectionManager tcManager, BloomWebSocketServer webSocketServer, BookCollectionHolder bookCollectionHolder, LocalizationChangedEvent localizationChangedEvent) { _bookSelection = bookSelection; _pathToCollection = pathToCollection; _collectionSettings = collectionSettings; _sourceCollectionsList = sourceCollectionsList; _bookCollectionFactory = bookCollectionFactory; _editBookCommand = editBookCommand; _bookServer = bookServer; _currentEditableCollectionSelection = currentEditableCollectionSelection; _thumbNailer = thumbNailer; _tcManager = tcManager; _webSocketServer = webSocketServer; _bookCollectionHolder = bookCollectionHolder; _localizationChangedEvent = localizationChangedEvent; createFromSourceBookCommand.Subscribe(CreateFromSourceBook); }
public LibraryModel(string pathToLibrary, CollectionSettings collectionSettings, //SendReceiver sendReceiver, BookSelection bookSelection, SourceCollectionsList sourceCollectionsList, BookCollection.Factory bookCollectionFactory, EditBookCommand editBookCommand, CreateFromSourceBookCommand createFromSourceBookCommand, BookServer bookServer, CurrentEditableCollectionSelection currentEditableCollectionSelection, BookThumbNailer thumbNailer, TeamCollectionManager tcManager) { _bookSelection = bookSelection; _pathToLibrary = pathToLibrary; _collectionSettings = collectionSettings; //_sendReceiver = sendReceiver; _sourceCollectionsList = sourceCollectionsList; _bookCollectionFactory = bookCollectionFactory; _editBookCommand = editBookCommand; _bookServer = bookServer; _currentEditableCollectionSelection = currentEditableCollectionSelection; _thumbNailer = thumbNailer; _tcManager = tcManager; createFromSourceBookCommand.Subscribe(CreateFromSourceBook); }
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 WorkspaceModel(BookSelection bookSelection, string directoryPath, TeamCollectionManager tcManager, CollectionSettings collectionSettings) { _bookSelection = bookSelection; _directoryPath = directoryPath; _tcManager = tcManager; _collectionSettings = collectionSettings; _bookSelection.SelectionChanged += OnSelectionChanged; }
/// <summary> /// Set a new TC status image. Called at Idle time or startup, on the UI thread. /// N.B.: It also gets called if the user tries to do something and the TeamCollection suddenly /// recognizes it is in a disconnected state. /// </summary> public void SetTeamCollectionStatus(TeamCollectionManager tcManager) { _tcStatusButton.Update(tcManager.CollectionStatus); // This will cause the CollectionsTabBookPane to reload the status of the book // (and the collection itself), which will trickle down to the status panel. if (tcManager.CollectionStatus == TeamCollectionStatus.Disconnected) { _webSocketServer.SendEvent("bookStatus", "reload"); } }
public BookServer(Book.Factory bookFactory, BookStorage.Factory storageFactory, BookStarter.Factory bookStarterFactory, Configurator.Factory configuratorFactory, TeamCollectionManager tcManager = null) { _bookFactory = bookFactory; _storageFactory = storageFactory; _bookStarterFactory = bookStarterFactory; _configuratorFactory = configuratorFactory; _tcManager = tcManager; }
public void OneTimeSetUp() { _localCollection = new TemporaryFolder("TeamCollectionApiTests"); var collectionPath = Path.Combine(_localCollection.FolderPath, Path.ChangeExtension(Path.GetFileName(_localCollection.FolderPath), ".bloomCollection")); _tcManager = new TeamCollectionManager(collectionPath, new BloomWebSocketServer(), new BookRenamedEvent(), null, null, null, null); _api = new TeamCollectionApi(new CurrentEditableCollectionSelection(), new CollectionSettings(collectionPath), new BookSelection(), _tcManager, null, null, null); }
public void HandleBookRename_CaseChangeOnly_WorksRight() { // Setup // const string originalBookName = "A new book"; var bookBuilder = new BookFolderBuilder() .WithRootFolder(_collectionFolder.FolderPath) .WithTitle(originalBookName) .WithHtm("<html><body>This is just a dummy</body></html>") .Build(); string bookFolderPath = bookBuilder.BuiltBookFolderPath; string htmlPath = bookBuilder.BuiltBookHtmPath; TeamCollectionManager.ForceCurrentUserForTests("*****@*****.**"); _collection.PutBook(bookFolderPath); var locked = _collection.AttemptLock(originalBookName); Assert.That(locked, Is.True, "successfully checked out book to [email protected]"); // SUT: rename changes status in local collection folder, but not in shared repo folder const string newBookName = "A New Book"; var newBookFolderPath = Path.Combine(_collectionFolder.FolderPath, newBookName); File.Move(htmlPath, Path.Combine(bookFolderPath, newBookName + ".htm")); // renaming directory doesn't work when names are 'the same' var tempPath = Path.Combine(_collectionFolder.FolderPath, "tempxxyy"); Directory.Move(bookFolderPath, tempPath); Directory.Move(tempPath, newBookFolderPath); _collection.HandleBookRename(originalBookName, newBookName); _collection.PutBook(newBookFolderPath, true); var newRepoPath = Path.Combine(_sharedFolder.FolderPath, "Books", newBookName + ".bloom"); // It should not have been deleted! This is a regression test for BL-10156. // The danger is that Windows considers the old and new names the same, so after // we move the file to the new name, if we go to delete the old name, we get rid of the new one. Assert.That(File.Exists(newRepoPath)); // Did it get renamed? var matchingFiles = Directory.EnumerateFiles(Path.Combine(_sharedFolder.FolderPath, "Books"), newBookName + ".bloom").ToArray(); Assert.That(matchingFiles[0], Is.EqualTo(Path.Combine(_sharedFolder.FolderPath, "Books", newBookName + ".bloom"))); var newStatus = _collection.GetLocalStatus(newBookName); var repoStatus = _collection.GetStatus(newBookName); Assert.That(newStatus, Is.Not.Null, "local status of renamed book is not null"); Assert.That(repoStatus, Is.Not.Null, "repo status of renamed book is not null"); Assert.That(newStatus.checksum, Is.EqualTo(repoStatus.checksum), "checksums of local and remote match after rename"); Assert.That(newStatus.lockedBy, Is.EqualTo(null), "lockedBy of local and remote match after rename"); Assert.That(newStatus.oldName, Is.Null, "local status has original name cleared after commit"); }
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 HandleModifiedFile_CheckedOutToMe_RemotelyToOther_RaisesCheckedOutByOtherAndErrorMessage() { // Setup // // Simulate a book was just overwritten with contents indicating a remote checkout, // while locally it is checked out to me. const string bookFolderName = "My conflict book"; var bookBuilder = new BookFolderBuilder() .WithRootFolder(_collectionFolder.FolderPath) .WithTitle(bookFolderName) .Build(); string bookFolderPath = bookBuilder.BuiltBookFolderPath; TeamCollectionManager.ForceCurrentUserForTests("*****@*****.**"); _collection.PutBook(bookFolderPath); // Temporarily, it looks locked by Nancy in both places. _collection.AttemptLock("My conflict book", "*****@*****.**"); var status = _collection.GetStatus("My conflict book").WithLockedBy(TeamCollectionManager.CurrentUser); // Now it is locally checked out to me. (The state changes are in the opposite order to what // we're trying to simulate, because we don't have an easy way to change remote checkout status without // changing local status to match at the same time.) _collection.WriteLocalStatus("My conflict book", status); var prevMessages = _tcLog.Messages.Count; // System Under Test...basically HandleModifiedFile, but this is a convenient place to // make sure we take the right path through this calling method. _collection.QueuePendingBookChange( new BookRepoChangeEventArgs() { BookFileName = $"{bookFolderName}.bloom" }); _collection.HandleRemoteBookChangesOnIdle(null, new EventArgs()); // Verification var eventArgs = (BookStatusChangeEventArgs)_mockTcManager.Invocations[2].Arguments[0]; Assert.That(eventArgs.CheckedOutByWhom, Is.EqualTo(CheckedOutBy.Other)); Assert.That(_tcLog.Messages[prevMessages].MessageType, Is.EqualTo(MessageAndMilestoneType.Error)); Assert.That(_tcLog.Messages[prevMessages].L10NId, Is.EqualTo("TeamCollection.ConflictingCheckout")); TeamCollectionManager.ForceCurrentUserForTests(null); }
public void HandleBookRename_CheckedOutToMe_FixesStatusProperly() { // Setup // const string originalBookName = "Hello. Goodbye!"; var bookBuilder = new BookFolderBuilder() .WithRootFolder(_collectionFolder.FolderPath) .WithTitle(originalBookName) .WithHtm("<html><body>This is just a dummy</body></html>") .Build(); string bookFolderPath = bookBuilder.BuiltBookFolderPath; string htmlPath = bookBuilder.BuiltBookHtmPath; TeamCollectionManager.ForceCurrentUserForTests("*****@*****.**"); _collection.PutBook(bookFolderPath); var locked = _collection.AttemptLock(originalBookName); Assert.That(locked, Is.True, "successfully checked out book to [email protected]"); // SUT: rename changes status in local collection folder, but not in shared repo folder const string newBookName = "Testing is Fun. Sometimes"; var newBookFolderPath = Path.Combine(_collectionFolder.FolderPath, newBookName); File.Move(htmlPath, Path.Combine(bookFolderPath, newBookName + ".htm")); Directory.Move(bookFolderPath, newBookFolderPath); _collection.HandleBookRename(originalBookName, newBookName); var newStatus = _collection.GetLocalStatus(newBookName); var repoStatus = _collection.GetStatus(newBookName); Assert.That(newStatus, Is.Not.Null, "local status of renamed book is not null"); Assert.That(repoStatus, Is.Not.Null, "repo status of renamed book is not null"); Assert.That(newStatus.checksum, Is.EqualTo(repoStatus.checksum), "checksums of local and remote match after rename"); Assert.That(newStatus.lockedBy, Is.EqualTo(repoStatus.lockedBy), "lockedBy of local and remote match after rename"); Assert.That(newStatus.lockedWhen, Is.EqualTo(repoStatus.lockedWhen), "lockedWhen of local and remote match after rename"); Assert.That(newStatus.lockedWhere, Is.EqualTo(repoStatus.lockedWhere), "lockedWhere of local and remote match after rename"); Assert.That(newStatus.oldName, Is.EqualTo(originalBookName), "local status has original name in oldName field after rename"); Assert.That(repoStatus.oldName, Is.Null, "repo status still has null oldName field after rename"); }
public BookCollection(string path, CollectionType collectionType, BookSelection bookSelection, TeamCollectionManager tcm = null, BloomWebSocketServer webSocketServer = null) { _path = path; _bookSelection = bookSelection; _tcManager = tcm; _webSocketServer = webSocketServer; Type = collectionType; if (collectionType == CollectionType.TheOneEditableCollection) { MakeCollectionCSSIfMissing(); } CollectionCreated?.Invoke(this, new EventArgs()); if (ContainsDownloadedBooks) { WatchDirectory(); } }
public void OneTimeTearDown() { _collectionFolder.Dispose(); _repoFolder.Dispose(); TeamCollectionManager.ForceCurrentUserForTests(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")); } } }
public ReadersApi(BookSelection _bookSelection, TeamCollectionManager tcManager) { this._bookSelection = _bookSelection; _tcManager = tcManager; }
public delegate CollectionTabView Factory(); //autofac uses this public CollectionTabView(CollectionModel model, SelectedTabChangedEvent selectedTabChangedEvent, TeamCollectionManager tcManager, BookSelection bookSelection, WorkspaceTabSelection tabSelection, BloomWebSocketServer webSocketServer, LocalizationChangedEvent localizationChangedEvent) { _model = model; _tabSelection = tabSelection; _bookSelection = bookSelection; _webSocketServer = webSocketServer; _tcManager = tcManager; BookCollection.CollectionCreated += OnBookCollectionCreated; InitializeComponent(); _reactControl.SetLocalizationChangedEvent(localizationChangedEvent); // after InitializeComponent, which creates it. BackColor = _reactControl.BackColor = Palette.GeneralBackground; _toolStrip.Renderer = new NoBorderToolStripRenderer(); _toolStripLeft.Renderer = new NoBorderToolStripRenderer(); // When going down to Shrink Stage 3 (see WorkspaceView), we want the right-side toolstrip to take precedence // (Settings, Other Collection). // This essentially makes the TC Status button's zIndex less than the buttons on the right side. _toolStripLeft.SendToBack(); //TODO splitContainer1.SplitterDistance = _collectionListView.PreferredWidth; if (SIL.PlatformUtilities.Platform.IsMono) { BackgroundColorsForLinux(); } selectedTabChangedEvent.Subscribe(c => { if (c.To == this) { Logger.WriteEvent("Entered Collections Tab"); if (_bookChangesPending && _bookSelection.CurrentSelection != null) { UpdateForBookChanges(_bookSelection.CurrentSelection); } } }); SetTeamCollectionStatus(tcManager); TeamCollectionManager.TeamCollectionStatusChanged += (sender, args) => { if (IsHandleCreated && !IsDisposed) { SafeInvoke.InvokeIfPossible("update TC status", this, false, () => SetTeamCollectionStatus(tcManager)); } }; _tcStatusButton.Click += (sender, args) => { // Reinstate this to see messages from before we started up. // We think it might be too expensive to show a list as long as this might get. // Instead, in the short term we may add a button to show the file. // Later we may implement some efficient way to scroll through them. // tcManager.CurrentCollection?.MessageLog?.LoadSavedMessages(); dynamic messageBundle = new DynamicJson(); messageBundle.showReloadButton = tcManager.MessageLog.ShouldShowReloadButton; _webSocketServer.LaunchDialog("TeamCollectionDialog", messageBundle); tcManager.CurrentCollectionEvenIfDisconnected?.MessageLog.WriteMilestone(MessageAndMilestoneType.LogDisplayed); }; // We don't want this control initializing until team collections sync (if any) is done. // That could change, but for now we're not trying to handle async changes arriving from // the TC to the local collection, and as part of that, the collection tab doesn't expect // the local collection to change because of TC stuff once it starts loading. Controls.Remove(_reactControl); bookSelection.SelectionChanged += (sender, e) => BookSelectionChanged(bookSelection.CurrentSelection); }
public static int Handle(HydrateParameters options) { if (!Directory.Exists(options.Path)) { if (options.Path.Contains(".htm")) { Debug.WriteLine("Supply only the directory, not the path to the file."); Console.Error.WriteLine("Supply only the directory, not the path to the file."); } else { Debug.WriteLine("Could not find " + options.Path); Console.Error.WriteLine("Could not find " + options.Path); } return(1); } Console.WriteLine("Starting Hydrating."); var layout = new Layout { SizeAndOrientation = SizeAndOrientation.FromString(options.SizeAndOrientation) }; if (File.Exists(TeamCollectionManager.GetTcLinkPathFromLcPath(Path.GetDirectoryName(options.Path)))) { throw new ApplicationException("Hydrate command cannot currently be used in Team Collections"); // To make this possible, we'd need to spin up a TeamCollectionManager and TeamCollection and pass the latter // to the Book as its SaveContext and still changes would be forbidden unless the book was checked out. } var collectionSettings = new CollectionSettings { XMatterPackName = options.XMatter, Language1Iso639Code = options.VernacularIsoCode, Language2Iso639Code = string.IsNullOrWhiteSpace(options.NationalLanguage1IsoCode) ? options.VernacularIsoCode : options.NationalLanguage1IsoCode, Language3Iso639Code = options.NationalLanguage2IsoCode }; XMatterPackFinder xmatterFinder = new XMatterPackFinder(new[] { BloomFileLocator.GetFactoryXMatterDirectory(), }); var locator = new BloomFileLocator(collectionSettings, xmatterFinder, ProjectContext.GetFactoryFileLocations(), ProjectContext.GetFoundFileLocations(), ProjectContext.GetAfterXMatterFileLocations()); // alwaysSaveable is fine here, as we already checked it's not a TC. var bookInfo = new BookInfo(options.Path, true, new AlwaysEditSaveContext()); var book = new Book.Book(bookInfo, new BookStorage(options.Path, locator, new BookRenamedEvent(), collectionSettings), null, collectionSettings, null, null, new BookRefreshEvent(), new BookSavedEvent()); // This was added as part of the phase 1 changes towards the new language system, where book languages // are more clearly distinct from collection languages, and there's no sense (except underlying storage) in which // a book has languages that are not selected for display. This made it necessary to decide explicitly // whether passing the national language options implies that a book is bi- or tri-lingual. Andrew and I (JohnT) // could not think of any reason to pass the arguments at all except to achieve that, so I made it so. var langs = new List <string>(); langs.Add(options.VernacularIsoCode); if (!string.IsNullOrEmpty(options.NationalLanguage1IsoCode) && options.NationalLanguage1IsoCode != options.VernacularIsoCode) { langs.Add(options.NationalLanguage1IsoCode); } if (!string.IsNullOrEmpty(options.NationalLanguage2IsoCode)) { langs.Add(options.NationalLanguage2IsoCode); } book.SetMultilingualContentLanguages(langs.ToArray()); //we might change this later, or make it optional, but for now, this will prevent surprises to processes //running this CLI... the folder name won't change out from under it. book.LockDownTheFileAndFolderName = true; book.SetLayout(layout); book.BringBookUpToDate(new NullProgress()); Console.WriteLine("Finished Hydrating."); Debug.WriteLine("Finished Hydrating."); return(0); }
public static void ChangeLayoutForAllContentPagesInAllBooks(IProgress progress, string collectionPath, string bookPath, string pageGuid) { if (!File.Exists(bookPath)) { MessageBox.Show("Could not find template book " + bookPath); return; } if (!File.Exists(collectionPath)) { MessageBox.Show("Could not find collection file " + collectionPath); return; } var collectionFolder = Path.GetDirectoryName(collectionPath); if (File.Exists(TeamCollectionManager.GetTcLinkPathFromLcPath(collectionFolder))) { MessageBox.Show("Change Layout command cannot currently be used in Team Collections"); return; // To make this possible, we'd need to spin up a TeamCollectionManager and TeamCollection and pass the latter // to the Book as its SaveContext and still changes would be forbidden unless every book was checked out. } var problems = new StringBuilder(); var collection = new BookCollection(collectionFolder, BookCollection.CollectionType.TheOneEditableCollection, new BookSelection(), null); var collectionSettings = new CollectionSettings(collectionPath); XMatterPackFinder xmatterFinder = new XMatterPackFinder(new[] { BloomFileLocator.GetFactoryXMatterDirectory() }); var locator = new BloomFileLocator(collectionSettings, xmatterFinder, ProjectContext.GetFactoryFileLocations(), ProjectContext.GetFoundFileLocations(), ProjectContext.GetAfterXMatterFileLocations()); // AlwaysSaveable is fine here, as we checked above that it's not a TC. var templateBookInfo = new BookInfo(Path.GetDirectoryName(bookPath), true, new AlwaysEditSaveContext()); var templateBook = new Book.Book(templateBookInfo, new BookStorage(templateBookInfo.FolderPath, locator, new BookRenamedEvent(), collectionSettings), null, collectionSettings, null, null, new BookRefreshEvent(), new BookSavedEvent(), new NoEditSaveContext()); var pageDictionary = templateBook.GetTemplatePagesIdDictionary(); IPage page = null; if (!pageDictionary.TryGetValue(pageGuid, out page)) { MessageBox.Show("Could not find template page " + pageGuid); return; } int i = 0; foreach (var bookInfo in collection.GetBookInfos()) { i++; try { var book = new Book.Book(bookInfo, new BookStorage(bookInfo.FolderPath, locator, new BookRenamedEvent(), collectionSettings), null, collectionSettings, null, null, new BookRefreshEvent(), new BookSavedEvent()); //progress.WriteMessage("Processing " + book.TitleBestForUserDisplay + " " + i + "/" + collection.GetBookInfos().Count()); progress.ProgressIndicator.PercentCompleted = i * 100 / collection.GetBookInfos().Count(); book.ChangeLayoutForAllContentPages(page); } catch (Exception ex) { Debug.WriteLine(ex.Message); Console.WriteLine(ex.Message); Console.WriteLine(ex.StackTrace); problems.AppendLine(Path.GetFileName(bookInfo.FolderPath)); } } if (problems.Length == 0) { MessageBox.Show("All books converted successfully"); } else { MessageBox.Show("Bloom had problems converting the following books; please check them:\n" + problems); } }
public CollectionSettingsDialog(CollectionSettings collectionSettings, XMatterPackFinder xmatterPackFinder, QueueRenameOfCollection queueRenameOfCollection, PageRefreshEvent pageRefreshEvent, TeamCollectionManager tcManager) { _collectionSettings = collectionSettings; _xmatterPackFinder = xmatterPackFinder; _queueRenameOfCollection = queueRenameOfCollection; _pageRefreshEvent = pageRefreshEvent; InitializeComponent(); // moved from the Designer where it was deleted if the Designer was touched _xmatterList.Columns.AddRange(new[] { new ColumnHeader() { Width = 250 } }); _language1Name.UseMnemonic = false; // Allow & to be part of the language display names. _language2Name.UseMnemonic = false; // This may be unlikely, but can't be ruled out. _language3Name.UseMnemonic = false; // See https://issues.bloomlibrary.org/youtrack/issue/BL-9919. _language1FontLabel.UseMnemonic = false; _language2FontLabel.UseMnemonic = false; _language3FontLabel.UseMnemonic = false; CollectionSettingsApi.DialogBeingEdited = this; if (_collectionSettings.IsSourceCollection) { _language1Label.Text = LocalizationManager.GetString("CollectionSettingsDialog.LanguageTab.Language1InSourceCollection", "Language 1", "In a local language collection, we say 'Local Language', but in a source collection, Local Language has no relevance, so we use this different label"); _language2Label.Text = LocalizationManager.GetString("CollectionSettingsDialog.LanguageTab.Language2InSourceCollection", "Language 2", "In a local language collection, we say 'Language 2 (e.g. National Language)', but in a source collection, National Language has no relevance, so we use this different label"); _language3Label.Text = LocalizationManager.GetString("CollectionSettingsDialog.LanguageTab.Language3InSourceCollection", "Language 3", "In a local language collection, we say 'Language 3 (e.g. Regional Language)', but in a source collection, National Language has no relevance, so we use this different label"); } _showExperimentalBookSources.Checked = ExperimentalFeatures.IsFeatureEnabled(ExperimentalFeatures.kExperimentalSourceBooks); _allowTeamCollection.Checked = ExperimentalFeatures.IsFeatureEnabled(ExperimentalFeatures.kTeamCollections); if (!ExperimentalFeatures.IsFeatureEnabled(ExperimentalFeatures.kTeamCollections) && tcManager.CurrentCollectionEvenIfDisconnected == null) { this._tab.Controls.Remove(this._teamCollectionTab); } // Don't allow the user to disable the Team Collection feature if we're currently in a Team Collection. _allowTeamCollection.Enabled = !(_allowTeamCollection.Checked && tcManager.CurrentCollectionEvenIfDisconnected != null); // AutoUpdate applies only to Windows: see https://silbloom.myjetbrains.com/youtrack/issue/BL-2317. if (SIL.PlatformUtilities.Platform.IsWindows) { _automaticallyUpdate.Checked = Settings.Default.AutoUpdate; } else { _automaticallyUpdate.Hide(); } // Without this, PendingDefaultBookshelf stays null unless the user changes it. // The result is the bookshelf selection gets cleared when other collection settings are saved. See BL-10093. PendingDefaultBookshelf = _collectionSettings.DefaultBookshelf; // _showSendReceive.CheckStateChanged += (sender, args) => // { // Settings.Default.ShowSendReceive = _showSendReceive.CheckState == // CheckState.Checked; // // _restartRequired = true; // UpdateDisplay(); // }; CollectionSettingsApi.BrandingChangeHandler = ChangeBranding; SetupEnterpriseBrowser(); TeamCollectionApi.TheOneInstance.SetCallbackToReopenCollection(() => { _restartRequired = true; ReactDialog.CloseCurrentModal(); // close the top Create dialog _okButton_Click(null, null); // close this dialog }); UpdateDisplay(); if (CollectionSettingsApi.FixEnterpriseSubscriptionCodeMode) { _tab.SelectedTab = _enterpriseTab; } if (tcManager.CurrentCollectionEvenIfDisconnected == null) { _noRenameTeamCollectionLabel.Visible = false; } else { _bloomCollectionName.Enabled = false; } // This code would mostly more naturally go in Designer. Unfortunately we can't run designer // until we get back in a state where all our dependencies are sufficiently consistent. _defaultBookshelfControl = ReactControl.Create("defaultBookshelfControlBundle"); tabPage2.Controls.Add(_defaultBookshelfControl); _defaultBookshelfControl.Location = new Point(_xmatterDescription.Left, _xmatterDescription.Bottom + 30); // We'd like it to be as big as possible, not just big enough for the immediate content. // Until React takes over at least the whole tab, the pull-down part of the combo can't // stretch outside the Gecko control. _defaultBookshelfControl.Size = new Size(_xmatterList.Width, 200); }
public ReactCollectionTabView(LibraryModel model, LibraryListView.Factory libraryListViewFactory, LibraryBookView.Factory templateBookViewFactory, SelectedTabChangedEvent selectedTabChangedEvent, SendReceiveCommand sendReceiveCommand, TeamCollectionManager tcManager) { _model = model; InitializeComponent(); BackColor = _reactControl.BackColor = Palette.GeneralBackground; _toolStrip.Renderer = new NoBorderToolStripRenderer(); _toolStripLeft.Renderer = new NoBorderToolStripRenderer(); // When going down to Shrink Stage 3 (see WorkspaceView), we want the right-side toolstrip to take precedence // (Settings, Other Collection). // This essentially makes the TC Status button's zIndex less than the buttons on the right side. _toolStripLeft.SendToBack(); //TODO splitContainer1.SplitterDistance = _collectionListView.PreferredWidth; _makeBloomPackButton.Visible = model.IsShellProject; _sendReceiveButton.Visible = Settings.Default.ShowSendReceive; if (sendReceiveCommand != null) { #if Chorus _sendReceiveButton.Click += (x, y) => sendReceiveCommand.Raise(this); _sendReceiveButton.Enabled = !SendReceiver.SendReceiveDisabled; #endif } else { _sendReceiveButton.Enabled = false; } if (SIL.PlatformUtilities.Platform.IsMono) { BackgroundColorsForLinux(); } selectedTabChangedEvent.Subscribe(c => { if (c.To == this) { Logger.WriteEvent("Entered Collections Tab"); } }); SetTeamCollectionStatus(tcManager); TeamCollectionManager.TeamCollectionStatusChanged += (sender, args) => { return; if (!IsDisposed) { SafeInvoke.InvokeIfPossible("update TC status", this, false, () => SetTeamCollectionStatus(tcManager)); } }; _tcStatusButton.Click += (sender, args) => { // Any messages for which reloading the collection is a useful action? var showReloadButton = tcManager.MessageLog.ShouldShowReloadButton; // Reinstate this to see messages from before we started up. // We think it might be too expensive to show a list as long as this might get. // Instead, in the short term we may add a button to show the file. // Later we may implement some efficient way to scroll through them. // tcManager.CurrentCollection?.MessageLog?.LoadSavedMessages(); using (var dlg = new ReactDialog("teamCollectionDialog", new { showReloadButton })) { dlg.ShowDialog(this); tcManager.CurrentCollectionEvenIfDisconnected?.MessageLog.WriteMilestone(MessageAndMilestoneType.LogDisplayed); } }; // We don't want this control initializing until team collections sync (if any) is done. // That could change, but for now we're not trying to handle async changes arriving from // the TC to the local collection, and as part of that, the collection tab doesn't expect // the local collection to change because of TC stuff once it starts loading. Controls.Remove(_reactControl); }
/// <summary> /// Set a new TC status image. Called at Idle time or startup, on the UI thread. /// </summary> public void SetTeamCollectionStatus(TeamCollectionManager tcManager) { _tcStatusButton.Update(tcManager.CollectionStatus); }
public void TearDown() { TeamCollectionManager.ForceCurrentUserForTests(null); _collectionFolder.Dispose(); _sharedFolder.Dispose(); }
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 CollectionSettingsDialog(CollectionSettings collectionSettings, QueueRenameOfCollection queueRenameOfCollection, PageRefreshEvent pageRefreshEvent, TeamCollectionManager tcManager, XMatterPackFinder xmatterPackFinder) { _collectionSettings = collectionSettings; _queueRenameOfCollection = queueRenameOfCollection; _pageRefreshEvent = pageRefreshEvent; _xmatterPackFinder = xmatterPackFinder; InitializeComponent(); _language1Name.UseMnemonic = false; // Allow & to be part of the language display names. _language2Name.UseMnemonic = false; // This may be unlikely, but can't be ruled out. _language3Name.UseMnemonic = false; // See https://issues.bloomlibrary.org/youtrack/issue/BL-9919. PendingFontSelections[0] = _collectionSettings.LanguagesZeroBased[0].FontName; PendingFontSelections[1] = _collectionSettings.LanguagesZeroBased[1].FontName; var have3rdLanguage = _collectionSettings.LanguagesZeroBased[2] != null; PendingFontSelections[2] = have3rdLanguage ? _collectionSettings.LanguagesZeroBased[2].FontName : ""; PendingNumberingStyle = _collectionSettings.PageNumberStyle; PendingXmatter = _collectionSettings.XMatterPackName; CollectionSettingsApi.DialogBeingEdited = this; if (_collectionSettings.IsSourceCollection) { _language1Label.Text = LocalizationManager.GetString("CollectionSettingsDialog.LanguageTab.Language1InSourceCollection", "Language 1", "In a local language collection, we say 'Local Language', but in a source collection, Local Language has no relevance, so we use this different label"); _language2Label.Text = LocalizationManager.GetString("CollectionSettingsDialog.LanguageTab.Language2InSourceCollection", "Language 2", "In a local language collection, we say 'Language 2 (e.g. National Language)', but in a source collection, National Language has no relevance, so we use this different label"); _language3Label.Text = LocalizationManager.GetString("CollectionSettingsDialog.LanguageTab.Language3InSourceCollection", "Language 3", "In a local language collection, we say 'Language 3 (e.g. Regional Language)', but in a source collection, National Language has no relevance, so we use this different label"); } _showExperimentalBookSources.Checked = ExperimentalFeatures.IsFeatureEnabled(ExperimentalFeatures.kExperimentalSourceBooks); _allowTeamCollection.Checked = ExperimentalFeatures.IsFeatureEnabled(ExperimentalFeatures.kTeamCollections); _allowSpreadsheetImportExport.Checked = ExperimentalFeatures.IsFeatureEnabled(ExperimentalFeatures.kSpreadsheetImportExport); if (!ExperimentalFeatures.IsFeatureEnabled(ExperimentalFeatures.kTeamCollections) && tcManager.CurrentCollectionEvenIfDisconnected == null) { this._tab.Controls.Remove(this._teamCollectionTab); } // Don't allow the user to disable the Team Collection feature if we're currently in a Team Collection. _allowTeamCollection.Enabled = !(_allowTeamCollection.Checked && tcManager.CurrentCollectionEvenIfDisconnected != null); // AutoUpdate applies only to Windows: see https://silbloom.myjetbrains.com/youtrack/issue/BL-2317. if (SIL.PlatformUtilities.Platform.IsWindows) { _automaticallyUpdate.Checked = Settings.Default.AutoUpdate; } else { _automaticallyUpdate.Hide(); } // Without this, PendingDefaultBookshelf stays null unless the user changes it. // The result is the bookshelf selection gets cleared when other collection settings are saved. See BL-10093. PendingDefaultBookshelf = _collectionSettings.DefaultBookshelf; CollectionSettingsApi.BrandingChangeHandler = ChangeBranding; TeamCollectionApi.TheOneInstance.SetCallbackToReopenCollection(() => { _restartRequired = true; ReactDialog.CloseCurrentModal(); // close the top Create dialog _okButton_Click(null, null); // close this dialog }); UpdateDisplay(); if (CollectionSettingsApi.FixEnterpriseSubscriptionCodeMode) { _tab.SelectedTab = _enterpriseTab; } if (tcManager.CurrentCollectionEvenIfDisconnected == null) { _noRenameTeamCollectionLabel.Visible = false; } else { _bloomCollectionName.Enabled = false; } }
public LibraryView(LibraryModel model, LibraryListView.Factory libraryListViewFactory, LibraryBookView.Factory templateBookViewFactory, SelectedTabChangedEvent selectedTabChangedEvent, SendReceiveCommand sendReceiveCommand, TeamCollectionManager tcManager) { _model = model; InitializeComponent(); splitContainer1.BackColor = Palette.BookListSplitterColor; // controls the left vs. right splitter _toolStrip.Renderer = new NoBorderToolStripRenderer(); _toolStripLeft.Renderer = new NoBorderToolStripRenderer(); _collectionListView = libraryListViewFactory(); _collectionListView.Dock = DockStyle.Fill; splitContainer1.Panel1.Controls.Add(_collectionListView); _bookView = templateBookViewFactory(); _bookView.TeamCollectionMgr = tcManager; _bookView.Dock = DockStyle.Fill; splitContainer1.Panel2.Controls.Add(_bookView); // When going down to Shrink Stage 3 (see WorkspaceView), we want the right-side toolstrip to take precedence // (Settings, Other Collection). // This essentially makes the TC Status button's zIndex less than the buttons on the right side. _toolStripLeft.SendToBack(); splitContainer1.SplitterDistance = _collectionListView.PreferredWidth; _makeBloomPackButton.Visible = model.IsShellProject; _sendReceiveButton.Visible = Settings.Default.ShowSendReceive; if (sendReceiveCommand != null) { #if Chorus _sendReceiveButton.Click += (x, y) => sendReceiveCommand.Raise(this); _sendReceiveButton.Enabled = !SendReceiver.SendReceiveDisabled; #endif } else { _sendReceiveButton.Enabled = false; } if (SIL.PlatformUtilities.Platform.IsMono) { BackgroundColorsForLinux(); } selectedTabChangedEvent.Subscribe(c => { if (c.To == this) { Logger.WriteEvent("Entered Collections Tab"); } }); SetTeamCollectionStatus(tcManager); TeamCollectionManager.TeamCollectionStatusChanged += (sender, args) => { if (IsHandleCreated && !IsDisposed) { SafeInvoke.InvokeIfPossible("update TC status", this, false, () => SetTeamCollectionStatus(tcManager)); } }; _tcStatusButton.Click += (sender, args) => { // Any messages for which reloading the collection is a useful action? var showReloadButton = tcManager.MessageLog.ShouldShowReloadButton; // Reinstate this to see messages from before we started up. // We think it might be too expensive to show a list as long as this might get. // Instead, in the short term we may add a button to show the file. // Later we may implement some efficient way to scroll through them. // tcManager.CurrentCollection?.MessageLog?.LoadSavedMessages(); using (var dlg = new ReactDialog("teamCollectionDialogBundle", new { showReloadButton }, "Team Collection")) { dlg.ShowDialog(this); tcManager.CurrentCollectionEvenIfDisconnected?.MessageLog.WriteMilestone(MessageAndMilestoneType .LogDisplayed); } }; }