Exemple #1
0
        public void HandleCheckInCurrentBook(ApiRequest request)
        {
            Action <float> reportCheckinProgress = (fraction) =>
            {
                dynamic messageBundle = new DynamicJson();
                messageBundle.fraction = fraction;
                _socketServer.SendBundle("checkinProgress", "progress", messageBundle);
                // The status panel is supposed to be showing a progress bar in response to getting the bundle,
                // but since we're doing the checkin on the UI thread, it doesn't get painted without this.
                Application.DoEvents();
            };

            try
            {
                // Right before calling this API, the status panel makes a change that
                // should make the progress bar visible. But this method is running on
                // the UI thread so without this call it won't appear until later, when
                // we have Application.DoEvents() as part of reporting progress. We do
                // quite a bit on large books before the first file is written to the
                // zip, so one more DoEvents() here lets the bar appear at once.
                Application.DoEvents();
                _bookSelection.CurrentSelection.Save();
                if (!_tcManager.CheckConnection())
                {
                    request.Failed();
                    return;
                }

                var bookName = Path.GetFileName(_bookSelection.CurrentSelection.FolderPath);
                if (_tcManager.CurrentCollection.OkToCheckIn(bookName))
                {
                    // review: not super happy about this being here in the api. Was stymied by
                    // PutBook not knowing about the actual book object, but maybe that could be passed in.
                    // It's important that this is done BEFORE the checkin: we want other users to see the
                    // comment, and NOT see the pending comment as if it was their own if they check out.
                    var message = BookHistory.GetPendingCheckinMessage(_bookSelection.CurrentSelection);
                    BookHistory.AddEvent(_bookSelection.CurrentSelection, BookHistoryEventType.CheckIn, message);
                    BookHistory.SetPendingCheckinMessage(_bookSelection.CurrentSelection, "");
                    _tcManager.CurrentCollection.PutBook(_bookSelection.CurrentSelection.FolderPath, true, false, reportCheckinProgress);
                    reportCheckinProgress(0);                     // hides the progress bar (important if a different book has been selected that is still checked out)

                    Analytics.Track("TeamCollectionCheckinBook",
                                    new Dictionary <string, string>()
                    {
                        { "CollectionId", _settings?.CollectionId },
                        { "CollectionName", _settings?.CollectionName },
                        { "Backend", _tcManager?.CurrentCollection?.GetBackendType() },
                        { "User", CurrentUser },
                        { "BookId", _bookSelection?.CurrentSelection.ID },
                        { "BookName", _bookSelection?.CurrentSelection.Title }
                    });
                }
                else
                {
                    // We can't check in! The system has broken down...perhaps conflicting checkouts while offline.
                    // Save our version in Lost-and-Found
                    _tcManager.CurrentCollection.PutBook(_bookSelection.CurrentSelection.FolderPath, false, true, reportCheckinProgress);
                    reportCheckinProgress(0);                     // cleans up panel for next time
                    // overwrite it with the current repo version.
                    _tcManager.CurrentCollection.CopyBookFromRepoToLocal(bookName, dialogOnError: true);
                    // Force a full reload of the book from disk and update the UI to match.
                    _bookSelection.SelectBook(_bookServer.GetBookFromBookInfo(_bookSelection.CurrentSelection.BookInfo, true));
                    var msg = LocalizationManager.GetString("TeamCollection.ConflictingEditOrCheckout",
                                                            "Someone else has edited this book or checked it out even though you were editing it! Your changes have been saved to Lost and Found");
                    ErrorReport.NotifyUserOfProblem(msg);
                    Analytics.Track("TeamCollectionConflictingEditOrCheckout",
                                    new Dictionary <string, string>()
                    {
                        { "CollectionId", _settings?.CollectionId },
                        { "CollectionName", _settings?.CollectionName },
                        { "Backend", _tcManager?.CurrentCollection?.GetBackendType() },
                        { "User", CurrentUser },
                        { "BookId", _bookSelection?.CurrentSelection?.ID },
                        { "BookName", _bookSelection?.CurrentSelection?.Title }
                    });
                }
                UpdateUiForBook();
                request.PostSucceeded();

                Application.Idle += OnIdleConnectionCheck;
            }
            catch (Exception e)
            {
                reportCheckinProgress(0);                 // cleans up panel progress indicator
                var msgId      = "TeamCollection.ErrorCheckingBookIn";
                var msgEnglish = "Error checking in {0}: {1}";
                var log        = _tcManager?.CurrentCollection?.MessageLog;
                // Pushing an error into the log will show the Reload Collection button. It's not obvious this
                // is useful here, since we don't know exactly what went wrong. However, it at least gives the user
                // the option to try it.
                if (log != null)
                {
                    log.WriteMessage(MessageAndMilestoneType.Error, msgId, msgEnglish, _bookSelection?.CurrentSelection?.FolderPath, e.Message);
                }
                Logger.WriteError(String.Format(msgEnglish, _bookSelection?.CurrentSelection?.FolderPath, e.Message), e);
                NonFatalProblem.ReportSentryOnly(e, $"Something went wrong for {request.LocalPath()} ({_bookSelection?.CurrentSelection?.FolderPath})");
                request.Failed("checkin failed");
            }
        }
Exemple #2
0
        // Needs to be thread-safe
        private string GetBookStatusJson(string bookFolderName, Book.Book book)
        {
            string   whoHasBookLocked = null;
            DateTime whenLocked       = DateTime.MaxValue;
            bool     problem          = false;
            // bookFolderName may be null when no book is selected, e.g., after deleting one.
            var status = bookFolderName == null ? null :_tcManager.CurrentCollection?.GetStatus(bookFolderName);
            // At this level, we know this is the path to the .bloom file in the repo
            // (though if we implement another backend, we'll have to generalize the notion somehow).
            // For the Javascript, it's just an argument to pass to
            // CommonMessages.GetPleaseClickHereForHelpMessage(). It's only used if hasInvalidRepoData is non-empty.
            string clickHereArg = "";
            var    folderTC     = _tcManager.CurrentCollection as FolderTeamCollection;

            if (folderTC != null && bookFolderName != null)
            {
                clickHereArg = UrlPathString.CreateFromUnencodedString(folderTC.GetPathToBookFileInRepo(bookFolderName))
                               .UrlEncoded;
            }

            string hasInvalidRepoData = (status?.hasInvalidRepoData ?? false) ?
                                        (folderTC)?.GetCouldNotOpenCorruptZipMessage()
                                : "";

            if (bookFolderName == null)
            {
                return(JsonConvert.SerializeObject(
                           new
                {
                    // Keep this in sync with IBookTeamCollectionStatus defined in TeamCollectionApi.tsx
                    who = "",
                    whoFirstName = "",
                    whoSurname = "",
                    when = DateTime.Now.ToShortDateString(),
                    where = "",
                    currentUser = CurrentUser,
                    currentUserName = TeamCollectionManager.CurrentUserFirstName,
                    currentMachine = TeamCollectionManager.CurrentMachine,
                    problem = "",
                    hasInvalidRepoData = false,
                    clickHereArg = "",
                    changedRemotely = false,
                    disconnected = false,
                    newLocalBook = true,
                    checkinMessage = "",
                    isUserAdmin = _tcManager.OkToEditCollectionSettings
                }));
            }

            bool newLocalBook = false;

            try
            {
                whoHasBookLocked =
                    _tcManager.CurrentCollectionEvenIfDisconnected?.WhoHasBookLocked(bookFolderName);
                // It's debatable whether to use CurrentCollectionEvenIfDisconnected everywhere. For now, I've only changed
                // it for the two bits of information actually needed by the status panel when disconnected.
                whenLocked = _tcManager.CurrentCollection?.WhenWasBookLocked(bookFolderName) ??
                             DateTime.MaxValue;
                if (whoHasBookLocked == TeamCollection.FakeUserIndicatingNewBook)
                {
                    // This situation comes about from two different scenarios:
                    // 1) The user is creating a new book and TeamCollection status doesn't matter
                    // 2) The user is trying to check out an existing book and TeamCollectionManager
                    //    discovers [through CheckConnection()] that it is suddenly in a disconnected
                    //    state.
                    // In both cases, the current selected book is in view. The only way to tell
                    // these two situations apart is that in (1) book.IsSaveable is true
                    // and in (2) it is not.
                    // Or, book may be null because we're just getting a status to show in the list
                    // of all books. In that case, book is null, but it's fairly safe to assume it's a new local book.
                    if (book?.IsSaveable ?? true)
                    {
                        whoHasBookLocked = CurrentUser;
                        newLocalBook     = true;
                    }
                    else
                    {
                        whoHasBookLocked = null;
                    }
                }
                problem = _tcManager.CurrentCollection?.HasLocalChangesThatMustBeClobbered(bookFolderName) ?? false;
            }
            catch (Exception e) when(e is ICSharpCode.SharpZipLib.Zip.ZipException || e is IOException)
            {
                hasInvalidRepoData = (_tcManager.CurrentCollection as FolderTeamCollection)?.GetCouldNotOpenCorruptZipMessage();
            }

            // If the request asked for the book by name, we don't have an actual Book object.
            // However, it happens that those requests don't need the checkinMessage.
            var checkinMessage = book == null ? "" : BookHistory.GetPendingCheckinMessage(book);

            return(JsonConvert.SerializeObject(
                       new
            {
                // Keep this in sync with IBookTeamCollectionStatus defined in TeamCollectionApi.tsx
                who = whoHasBookLocked,
                whoFirstName = _tcManager.CurrentCollection?.WhoHasBookLockedFirstName(bookFolderName),
                whoSurname = _tcManager.CurrentCollection?.WhoHasBookLockedSurname(bookFolderName),
                when = whenLocked.ToLocalTime().ToShortDateString(),
                where = _tcManager.CurrentCollectionEvenIfDisconnected?.WhatComputerHasBookLocked(bookFolderName),
                currentUser = CurrentUser,
                currentUserName = TeamCollectionManager.CurrentUserFirstName,
                currentMachine = TeamCollectionManager.CurrentMachine,
                problem,
                hasInvalidRepoData,
                clickHereArg,
                changedRemotely = _tcManager.CurrentCollection?.HasBeenChangedRemotely(bookFolderName),
                disconnected = _tcManager.CurrentCollectionEvenIfDisconnected?.IsDisconnected,
                newLocalBook,
                checkinMessage,
                isUserAdmin = _tcManager.OkToEditCollectionSettings
            }));
        }