diff src/mainwindow.cpp @ 548:dca5bd5b2a06

Merge from branch "fswatcher"
author Chris Cannam
date Tue, 14 Feb 2012 17:55:39 +0000
parents 4edb47f8c3a3
children 06f7ae09015f
line wrap: on
line diff
--- a/src/mainwindow.cpp	Fri Feb 10 13:08:07 2012 +0000
+++ b/src/mainwindow.cpp	Tue Feb 14 17:55:39 2012 +0000
@@ -52,14 +52,12 @@
 #include "workstatuswidget.h"
 #include "hgignoredialog.h"
 #include "versiontester.h"
+#include "fswatcher.h"
 
 
 MainWindow::MainWindow(QString myDirPath) :
     m_myDirPath(myDirPath),
-    m_helpDialog(0),
-    m_fsWatcherGeneralTimer(0),
-    m_fsWatcherRestoreTimer(0),
-    m_fsWatcherSuspended(false)
+    m_helpDialog(0)
 {
     setWindowIcon(QIcon(":images/easyhg-icon.png"));
 
@@ -67,7 +65,11 @@
 
     m_showAllFiles = false;
 
-    m_fsWatcher = 0;
+    m_fsWatcher = new FsWatcher();
+    m_fsWatcherToken = m_fsWatcher->getNewToken();
+    m_commandSequenceInProgress = false;
+    connect(m_fsWatcher, SIGNAL(changed()), this, SLOT(checkFilesystem()));
+
     m_commitsSincePush = 0;
     m_shouldHgStat = true;
 
@@ -251,14 +253,16 @@
 {
     QStringList params;
 
-    if (m_showAllFiles) {
-        params << "stat" << "-A";
-    } else {
-        params << "stat" << "-ardum";
-    }
+    // We always stat all files, regardless of whether we're showing
+    // them all, because we need them for the filesystem monitor
+    params << "stat" << "-A";
 
     m_lastStatOutput = "";
 
+    // We're about to do a stat, so we can silently bring ourselves
+    // up-to-date on any file changes to this point
+    (void)m_fsWatcher->getChangedPaths(m_fsWatcherToken);
+
     m_runner->requestAction(HgAction(ACT_STAT, m_workFolderPath, params));
 }
 
@@ -1301,14 +1305,6 @@
     m_mergeCommitComment = "";
     m_stateUnknown = true;
     m_needNewLog = true;
-    if (m_fsWatcher) {
-        delete m_fsWatcherGeneralTimer;
-        m_fsWatcherGeneralTimer = 0;
-        delete m_fsWatcherRestoreTimer;
-        m_fsWatcherRestoreTimer = 0;
-        delete m_fsWatcher;
-        m_fsWatcher = 0;
-    }
 }
 
 void MainWindow::hgServe()
@@ -1821,126 +1817,22 @@
     }
 }
 
-void MainWindow::updateFileSystemWatcher()
+void MainWindow::updateFsWatcher()
 {
-    bool justCreated = false;
-    if (!m_fsWatcher) {
-        m_fsWatcher = new QFileSystemWatcher();
-        justCreated = true;
-    }
-
-    // QFileSystemWatcher will refuse to add a file or directory to
-    // its watch list that it is already watching -- fine, that's what
-    // we want -- but it prints a warning when this happens, which is
-    // annoying because it would be the normal case for us.  So we'll
-    // check for duplicates ourselves.
-    QSet<QString> alreadyWatched;
-    QStringList dl(m_fsWatcher->directories());
-    foreach (QString d, dl) alreadyWatched.insert(d);
-    
-    std::deque<QString> pending;
-    pending.push_back(m_workFolderPath);
-
-    while (!pending.empty()) {
-
-        QString path = pending.front();
-        pending.pop_front();
-        if (!alreadyWatched.contains(path)) {
-            m_fsWatcher->addPath(path);
-            DEBUG << "Added to file system watcher: " << path << endl;
-        }
-
-        QDir d(path);
-        if (d.exists()) {
-            d.setFilter(QDir::Dirs | QDir::NoDotAndDotDot |
-                        QDir::Readable | QDir::NoSymLinks);
-            foreach (QString entry, d.entryList()) {
-                if (entry.startsWith('.')) continue;
-                QString entryPath = d.absoluteFilePath(entry);
-                pending.push_back(entryPath);
-            }
-        }
-    }
-
-    // The general timer isn't really related to the fs watcher
-    // object, it just does something similar -- every now and then we
-    // do a refresh just to update the history dates etc
-
-    m_fsWatcherGeneralTimer = new QTimer(this);
-    connect(m_fsWatcherGeneralTimer, SIGNAL(timeout()),
-            this, SLOT(checkFilesystem()));
-    m_fsWatcherGeneralTimer->setInterval(30 * 60 * 1000); // half an hour
-    m_fsWatcherGeneralTimer->start();
-
-    if (justCreated) {
-        connect(m_fsWatcher, SIGNAL(directoryChanged(QString)),
-                this, SLOT(fsDirectoryChanged(QString)));
-        connect(m_fsWatcher, SIGNAL(fileChanged(QString)),
-                this, SLOT(fsFileChanged(QString)));
-    }
-}
-
-void MainWindow::suspendFileSystemWatcher()
-{
-    DEBUG << "MainWindow::suspendFileSystemWatcher" << endl;
-    if (m_fsWatcher) {
-        m_fsWatcherSuspended = true;
-        if (m_fsWatcherRestoreTimer) {
-            delete m_fsWatcherRestoreTimer;
-            m_fsWatcherRestoreTimer = 0;
-        }
-        m_fsWatcherGeneralTimer->stop();
-    }
-}
-
-void MainWindow::restoreFileSystemWatcher()
-{
-    DEBUG << "MainWindow::restoreFileSystemWatcher" << endl;
-    if (m_fsWatcherRestoreTimer) delete m_fsWatcherRestoreTimer;
-        
-    // The restore timer is used to leave a polite interval between
-    // being asked to restore the watcher and actually doing so.  It's
-    // a single shot timer each time it's used, but we don't use
-    // QTimer::singleShot because we want to stop the previous one if
-    // it's running (via deleting it)
-
-    m_fsWatcherRestoreTimer = new QTimer(this);
-    connect(m_fsWatcherRestoreTimer, SIGNAL(timeout()),
-            this, SLOT(actuallyRestoreFileSystemWatcher()));
-    m_fsWatcherRestoreTimer->setInterval(1000);
-    m_fsWatcherRestoreTimer->setSingleShot(true);
-    m_fsWatcherRestoreTimer->start();
-}
-
-void MainWindow::actuallyRestoreFileSystemWatcher()
-{
-    DEBUG << "MainWindow::actuallyRestoreFileSystemWatcher" << endl;
-    if (m_fsWatcher) {
-        m_fsWatcherSuspended = false;
-        m_fsWatcherGeneralTimer->start();
-    }
+    m_fsWatcher->setWorkDirPath(m_workFolderPath);
+    m_fsWatcher->setTrackedFilePaths(m_hgTabs->getFileStates().trackedFiles());
 }
 
 void MainWindow::checkFilesystem()
 {
     DEBUG << "MainWindow::checkFilesystem" << endl;
-    hgRefresh();
-}
-
-void MainWindow::fsDirectoryChanged(QString d)
-{
-    DEBUG << "MainWindow::fsDirectoryChanged " << d << endl;
-    if (!m_fsWatcherSuspended) {
-        hgStat();
+    if (!m_commandSequenceInProgress) {
+        if (!m_fsWatcher->getChangedPaths(m_fsWatcherToken).empty()) {
+            hgRefresh();
+            return;
+        }
     }
-}
-
-void MainWindow::fsFileChanged(QString f)
-{
-    DEBUG << "MainWindow::fsFileChanged " << f << endl;
-    if (!m_fsWatcherSuspended) {
-        hgStat();
-    }
+    updateFsWatcher();
 }
 
 QString MainWindow::format1(QString head)
@@ -2088,21 +1980,14 @@
 
 void MainWindow::commandStarting(HgAction action)
 {
-    // Annoyingly, hg stat actually modifies the working directory --
-    // it creates files called hg-checklink and hg-checkexec to test
-    // properties of the filesystem.  For safety's sake, suspend the
-    // fs watcher while running commands, and restore it shortly after
-    // a command has finished.
-
-    if (action.action == ACT_STAT) {
-        suspendFileSystemWatcher();
-    }
+    m_commandSequenceInProgress = true;
 }
 
-void MainWindow::commandFailed(HgAction action, QString stderr, QString stdout)
+void MainWindow::commandFailed(HgAction action, QString stdErr, QString stdOut)
 {
     DEBUG << "MainWindow::commandFailed" << endl;
-    restoreFileSystemWatcher();
+
+    m_commandSequenceInProgress = false;
 
     QString setstr;
 #ifdef Q_OS_MAC
@@ -2130,7 +2015,7 @@
              tr("Failed to run Mercurial"),
              tr("Failed to run Mercurial"),
              tr("The Mercurial program either could not be found or failed to run.<br><br>Check that the Mercurial program path is correct in %1.").arg(setstr),
-             stderr);
+             stdErr);
         settings(SettingsDialog::PathsTab);
         return;
     case ACT_TEST_HG_EXT:
@@ -2139,7 +2024,7 @@
              tr("Failed to run Mercurial"),
              tr("Failed to run Mercurial with extension enabled"),
              tr("The Mercurial program failed to run with the EasyMercurial interaction extension enabled.<br>This may indicate an installation problem.<br><br>You may be able to continue working if you switch off &ldquo;Use EasyHg Mercurial Extension&rdquo; in %1.  Note that remote repositories that require authentication might not work if you do this.").arg(setstr),
-             stderr);
+             stdErr);
         settings(SettingsDialog::PathsTab);
         return;
     case ACT_CLONEFROMREMOTE:
@@ -2148,20 +2033,20 @@
         enableDisableActions();
         break; // go on to default report
     case ACT_INCOMING:
-        if (stderr.contains("authorization failed")) {
-            reportAuthFailed(stderr);
+        if (stdErr.contains("authorization failed")) {
+            reportAuthFailed(stdErr);
             return;
-        } else if (stderr.contains("entry cancelled")) {
+        } else if (stdErr.contains("entry cancelled")) {
             // ignore this, user cancelled username or password dialog
             return;
         } else {
-            // Incoming returns non-zero code and no stderr if the
+            // Incoming returns non-zero code and no stdErr if the
             // check was successful but there are no changes
             // pending. This is the only case where we need to remove
             // warning messages, because it's the only case where a
             // non-zero code can be returned even though the command
             // has for our purposes succeeded
-            QString replaced = stderr;
+            QString replaced = stdErr;
             while (1) {
                 QString r1 = replaced;
                 r1.replace(QRegExp("warning: [^\\n]*"), "");
@@ -2175,31 +2060,33 @@
         }
         break; // go on to default report
     case ACT_PULL:
-        if (stderr.contains("authorization failed")) {
-            reportAuthFailed(stderr);
+        if (stdErr.contains("authorization failed")) {
+            reportAuthFailed(stdErr);
             return;
-        } else if (stderr.contains("entry cancelled")) {
+        } else if (stdErr.contains("entry cancelled")) {
             // ignore this, user cancelled username or password dialog
             return;
-        } else if (stderr.contains("no changes found") || stdout.contains("no changes found")) {
+        } else if (stdErr.contains("no changes found") || stdOut.contains("no changes found")) {
             // success: hg 2.1 starts returning failure code for empty pull/push
-            commandCompleted(action, stdout);
+            m_commandSequenceInProgress = true; // there may be further commands
+            commandCompleted(action, stdOut);
             return;
         }
         break; // go on to default report
     case ACT_PUSH:
-        if (stderr.contains("creates new remote head")) {
-            reportNewRemoteHeads(stderr);
+        if (stdErr.contains("creates new remote head")) {
+            reportNewRemoteHeads(stdErr);
             return;
-        } else if (stderr.contains("authorization failed")) {
-            reportAuthFailed(stderr);
+        } else if (stdErr.contains("authorization failed")) {
+            reportAuthFailed(stdErr);
             return;
-        } else if (stderr.contains("entry cancelled")) {
+        } else if (stdErr.contains("entry cancelled")) {
             // ignore this, user cancelled username or password dialog
             return;
-        } else if (stderr.contains("no changes found") || stdout.contains("no changes found")) {
+        } else if (stdErr.contains("no changes found") || stdOut.contains("no changes found")) {
             // success: hg 2.1 starts returning failure code for empty pull/push
-            commandCompleted(action, stdout);
+            m_commandSequenceInProgress = true; // there may be further commands
+            commandCompleted(action, stdOut);
             return;
         }
         break; // go on to default report
@@ -2209,22 +2096,23 @@
         // problem, something else will fail too).  Pretend it
         // succeeded, so that any further actions that are contingent
         // on the success of the heads query get carried out properly.
+        m_commandSequenceInProgress = true; // there may be further commands
         commandCompleted(action, "");
         return;
     case ACT_FOLDERDIFF:
     case ACT_CHGSETDIFF:
-        // external program, unlikely to be anything useful in stderr
+        // external program, unlikely to be anything useful in stdErr
         // and some return with failure codes when something as basic
         // as the user closing the window via the wm happens
         return;
     case ACT_MERGE:
-        if (stderr.contains("working directory ancestor")) {
+        if (stdErr.contains("working directory ancestor")) {
             // arguably we should prevent this upfront, but that's
             // trickier!
             MoreInformationDialog::information
                 (this, tr("Merge"), tr("Merge has no effect"),
                  tr("You asked to merge a revision with one of its ancestors.<p>This has no effect, because the ancestor's changes already exist in both revisions."),
-                 stderr);
+                 stdErr);
             return;
         }
         // else fall through
@@ -2232,7 +2120,7 @@
         MoreInformationDialog::information
             (this, tr("Merge"), tr("Merge failed"),
              tr("Some files were not merged successfully.<p>You can Merge again to repeat the interactive merge; use Revert to abandon the merge entirely; or edit the files that are in conflict in an editor and, when you are happy with them, choose Mark Resolved in each file's right-button menu."),
-             stderr);
+             stdErr);
         m_mergeCommitComment = "";
         return;
     case ACT_STAT:
@@ -2251,17 +2139,16 @@
         (this,
          tr("Command failed"),
          tr("Command failed"),
-         (stderr == "" ?
+         (stdErr == "" ?
           tr("A Mercurial command failed to run correctly.  This may indicate an installation problem or some other problem with EasyMercurial.") :
           tr("A Mercurial command failed to run correctly.  This may indicate an installation problem or some other problem with EasyMercurial.<br><br>See &ldquo;More Details&rdquo; for the command output.")),
-         stderr);
+         stdErr);
 }
 
 void MainWindow::commandCompleted(HgAction completedAction, QString output)
 {
 //    std::cerr << "commandCompleted: " << completedAction.action << std::endl;
 
-    restoreFileSystemWatcher();
     HGACTIONS action = completedAction.action;
 
     if (action == ACT_NONE) return;
@@ -2320,7 +2207,6 @@
 
     case ACT_STAT:
         m_lastStatOutput = output;
-        updateFileSystemWatcher();
         break;
 
     case ACT_RESOLVE_LIST:
@@ -2624,10 +2510,12 @@
     }
 
     if (noMore) {
+        m_commandSequenceInProgress = false;
         m_stateUnknown = false;
         enableDisableActions();
         m_hgTabs->updateHistory();
         updateRecentMenu();
+        checkFilesystem();
     }
 }
 
@@ -2996,7 +2884,7 @@
     m_exitAct->setStatusTip(tr("Exit EasyMercurial"));
 
     //Repository actions
-    m_hgRefreshAct = new QAction(QIcon(":/images/status.png"), tr("&Refresh"), this);
+    m_hgRefreshAct = new QAction(QIcon(":/images/status.png"), tr("&Re-Read Working Folder"), this);
     m_hgRefreshAct->setShortcut(tr("Ctrl+R"));
     m_hgRefreshAct->setStatusTip(tr("Refresh the window to show the current state of the working folder"));
 
@@ -3118,22 +3006,19 @@
 {
     int sz = 32;
 
-    m_fileToolBar = addToolBar(tr("File"));
-    m_fileToolBar->setIconSize(QSize(sz, sz));
-    m_fileToolBar->addAction(m_openAct);
-    m_fileToolBar->addAction(m_hgRefreshAct);
-    m_fileToolBar->setMovable(false);
-
-    m_repoToolBar = addToolBar(tr("Remote"));
-    m_repoToolBar->setIconSize(QSize(sz, sz));
-    m_repoToolBar->addAction(m_hgIncomingAct);
-    m_repoToolBar->addAction(m_hgPullAct);
-    m_repoToolBar->addAction(m_hgPushAct);
-    m_repoToolBar->setMovable(false);
+    bool spacingReqd = false;
+#ifndef Q_OS_MAC
+    spacingReqd = true;
+#endif
 
     m_workFolderToolBar = addToolBar(tr("Work"));
     addToolBar(Qt::LeftToolBarArea, m_workFolderToolBar);
     m_workFolderToolBar->setIconSize(QSize(sz, sz));
+    if (spacingReqd) {
+        QWidget *w = new QWidget;
+        w->setFixedHeight(6);
+        m_workFolderToolBar->addWidget(w);
+    }
     m_workFolderToolBar->addAction(m_hgFolderDiffAct);
     m_workFolderToolBar->addSeparator();
     m_workFolderToolBar->addAction(m_hgRevertAct);
@@ -3145,6 +3030,17 @@
     m_workFolderToolBar->addAction(m_hgRemoveAct);
     m_workFolderToolBar->setMovable(false);
 
+    m_repoToolBar = addToolBar(tr("Remote"));
+    m_repoToolBar->setIconSize(QSize(sz, sz));
+    if (spacingReqd) m_repoToolBar->addWidget(new QLabel(" "));
+    m_repoToolBar->addAction(m_openAct);
+    if (spacingReqd) m_repoToolBar->addWidget(new QLabel(" "));
+    m_repoToolBar->addSeparator();
+    m_repoToolBar->addAction(m_hgIncomingAct);
+    m_repoToolBar->addAction(m_hgPullAct);
+    m_repoToolBar->addAction(m_hgPushAct);
+    m_repoToolBar->setMovable(false);
+
     updateToolBarStyle();
 }