Esempio n. 1
0
        public void HandleChooseFolder(ApiRequest request)
        {
            var initialPath = request.GetParamOrNull("path");
            var description = request.GetParamOrNull("description");
            var forOutput   = request.GetParamOrNull("forOutput");
            var isForOutput = !String.IsNullOrEmpty(forOutput) && forOutput.ToLowerInvariant() == "true";

            dynamic result = new DynamicJson();
            var     book   = _bookSelection?.CurrentSelection;
            bool    repeat = false;

            do
            {
                using (var dlg = new FolderBrowserDialog())
                {
                    if (!String.IsNullOrEmpty(initialPath))
                    {
                        dlg.SelectedPath = initialPath;
                    }
                    dlg.ShowNewFolderButton = true;

                    if (!string.IsNullOrEmpty(description))
                    {
                        dlg.Description = description;
                    }

                    result.success = dlg.ShowDialog() == DialogResult.OK;
                    result.path    = result.success ? dlg.SelectedPath : "";
                    string collectionFolder = string.Empty;
                    repeat = result.success && isForOutput && Utils.MiscUtils.IsFolderInsideBloomCollection(result.path, out collectionFolder);
                    if (repeat)
                    {
                        Utils.MiscUtils.WarnUserOfInvalidFolderChoice(collectionFolder, result.path);
                        // Change the initialFolder to just above the collection folder, or to the documents folder
                        // if that ends up empty.
                        initialPath = Path.GetDirectoryName(collectionFolder);
                        if (String.IsNullOrEmpty(initialPath))
                        {
                            initialPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
                        }
                    }
                }
            } while (repeat);

            // We send the result through a websocket rather than simply returning it because
            // if the user is very slow (one site said FF times out after 90s) the browser may
            // abandon the request before it completes. The POST result is ignored and the
            // browser simply listens to the socket.
            // We'd prefer this request to return immediately and set a callback to run
            // when the dialog closes and handle the results, but FolderBrowserDialog
            // does not offer such an API. Instead, we just ignore any timeout
            // in our Javascript code.


            _webSocketServer.SendBundle("common", "chooseFolder-results", result);
            request.PostSucceeded();
        }
        public static void DoWorkWithProgressDialog(BloomWebSocketServer socketServer, string socketContext, Func <Form> makeDialog,
                                                    Func <IWebSocketProgress, bool> doWhat, Action <Form> doWhenMainActionFalse = null)
        {
            var progress = new WebSocketProgress(socketServer, socketContext);

            // NOTE: This (specifically ShowDialog) blocks the main thread until the dialog is closed.
            // Be careful to avoid deadlocks.
            using (var dlg = makeDialog())
            {
                // For now let's not try to handle letting the user abort.
                dlg.ControlBox = false;
                var worker = new BackgroundWorker();
                worker.DoWork += (sender, args) =>
                {
                    // A way of waiting until the dialog is ready to receive progress messages
                    while (!socketServer.IsSocketOpen(socketContext))
                    {
                        Thread.Sleep(50);
                    }
                    bool waitForUserToCloseDialogOrReportProblems;
                    try
                    {
                        waitForUserToCloseDialogOrReportProblems = doWhat(progress);
                    }
                    catch (Exception ex)
                    {
                        // depending on the nature of the problem, we might want to do more or less than this.
                        // But at least this lets the dialog reach one of the states where it can be closed,
                        // and gives the user some idea things are not right.
                        socketServer.SendEvent(socketContext, "finished");
                        waitForUserToCloseDialogOrReportProblems = true;
                        progress.MessageWithoutLocalizing("Something went wrong: " + ex.Message, ProgressKind.Error);
                    }

                    // stop the spinner
                    socketServer.SendEvent(socketContext, "finished");
                    if (waitForUserToCloseDialogOrReportProblems)
                    {
                        // Now the user is allowed to close the dialog or report problems.
                        // (ProgressDialog in JS-land is watching for this message, which causes it to turn
                        // on the buttons that allow the dialog to be manually closed (or a problem to be reported).
                        socketServer.SendBundle(socketContext, "show-buttons", new DynamicJson());
                    }
                    else
                    {
                        // Just close the dialog
                        dlg.Invoke((Action)(() =>
                        {
                            if (doWhenMainActionFalse != null)
                            {
                                doWhenMainActionFalse(dlg);
                            }
                            else
                            {
                                dlg.Close();
                            }
                        }));
                    }
                };

                worker.RunWorkerAsync();
                dlg.ShowDialog();                 // returns when dialog closed
            }
        }
Esempio n. 3
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))
                {
                    _tcManager.CurrentCollection.PutBook(_bookSelection.CurrentSelection.FolderPath, true, false, reportCheckinProgress);
                    reportCheckinProgress(0);                     // cleans up panel for next time
                    // 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.
                    BookHistory.AddEvent(_bookSelection.CurrentSelection, BookHistoryEventType.CheckIn);

                    _tcManager.CurrentCollection.PutBook(_bookSelection.CurrentSelection.FolderPath, checkin: true);

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