Uploaded image for project: 'Qt'
  1. Qt
  2. QTBUG-61792

QFileSystemWatcher removePath() Fails On Windows when directory and files within directory are watched

    XMLWordPrintable

    Details

    • Type: Bug
    • Status: Closed
    • Priority: P3: Somewhat important
    • Resolution: Done
    • Affects Version/s: 4.8.x, 5.6.2, 5.9.0
    • Fix Version/s: 5.9.2
    • Labels:
      None
    • Environment:
      Windows 8.1, Qt 5.9.0, MingGW 5.3.0
    • Commits:
      e1d7f7dfbc0191f585fde8695d315e3ffb98ccc5 (qtbase/5.9, 7.7.2017, 5.9.2.)

      Description

      There is an issue whereby QFileSystemWatcher::removePath() will consistently fail to remove files or directories.  This issue is exclusive to Windows and occurs on both Windows 8.1 and Windows 10 (1703), but does not occur on other operating systems (tested Linux Mint 18 and MacOS 10.12.5 without problems).  I was using Qt 5.6.2, so I tested Qt 5.9.0, but found the issue is still present in that release.  The bug can take two different forms depending on the order you add items to the QFileSystemWatcher.

      1) QFileSystemWatcher::removePath() Fails to Remove Directories

      Steps to recreate:

      • Add a directory to a QFileSystemWatcher with addPath().
      • Add some files from that directory to the same QFileSystemWatcher with addPath() or addPaths().
      • Try to remove the directory with removePath() - the operation will fail every time.
      • The files can be successfully removed, but even after removing the files, attempts to remove the directory will fail.
      • The directory will still be present on the list returned by directories(), but no directoryChanged() signal will be emitted if the contents of the directory changes.
      • If you try to re-add the directory with addPath(), the operation will fail because the directory is already on the watch list.  This means that there's no way to get directoryChanged() signals working again after a failed attempt to remove a directory from the QFileSystemWatcher.

      Sample Program to Illustrate Problem

      #include <QCoreApplication>
      #include <QFileSystemWatcher>
      #include <QDebug>
      
      void RemoveItems(QFileSystemWatcher & fsWatcher, const char* desc, const QStringList & itemList);
      void PrintLists(const QStringList & dirList, const QStringList & fileList);
      void PrintList(const QStringList & itemList);
      
      int main(int argc, char *argv[])
      {
          QCoreApplication a(argc, argv);
      
          QFileSystemWatcher fsWatcher;
      
          fsWatcher.addPath("G:/TestFiles/Test1");
          fsWatcher.addPath("G:/TestFiles/Test1/File 1-1.txt");
          fsWatcher.addPath("G:/TestFiles/Test1/File 1-2.txt");
          fsWatcher.addPath("G:/TestFiles/Test1/File 1-3.txt");
      
          RemoveItems(fsWatcher, "Directories", fsWatcher.directories());
          RemoveItems(fsWatcher, "Files", fsWatcher.files());
      
          PrintLists(fsWatcher.directories(), fsWatcher.files());
      
          fsWatcher.addPath("G:/TestFiles/Test2");
          fsWatcher.addPath("G:/TestFiles/Test2/File 2-1.txt");
          fsWatcher.addPath("G:/TestFiles/Test2/File 2-2.txt");
          fsWatcher.addPath("G:/TestFiles/Test2/File 2-3.txt");
      
          RemoveItems(fsWatcher, "Directories", fsWatcher.directories());
          RemoveItems(fsWatcher, "Files", fsWatcher.files());
      
          PrintLists(fsWatcher.directories(), fsWatcher.files());
      
          return a.exec();
      }
      
      
      void RemoveItems(QFileSystemWatcher & fsWatcher, const char* desc, const QStringList & itemList)
      {
          printf("Removing %d %s\n", itemList.size(), desc);
          QStringList failList = fsWatcher.removePaths(itemList);
      
          if (failList.isEmpty())
          {
              printf("%s removed successfully\n", desc);
          }
          else
          {
              QStringList::const_iterator item;
              for (item = failList.constBegin() ; item != failList.constEnd() ; ++item)
                  printf("Remove fail - %s\n", item->toLatin1().data());
          }
          printf("\n");
      }
      
      
      void PrintLists(const QStringList & dirList, const QStringList & fileList)
      {
          printf("Current Watch List\n");
          if (dirList.isEmpty() && fileList.isEmpty())
          {
             printf("[Empty]\n\n");
          }
          else
          {
              PrintList(dirList);
              PrintList(fileList);
              printf("\n");
          }
      }
      
      
      void PrintList(const QStringList & itemList)
      {
          if (itemList.isEmpty())
              return;
      
          QStringList::const_iterator item;
          for (item = itemList.constBegin() ; item != itemList.constEnd() ; ++item)
              printf("%s\n", item->toLatin1().data());
      }
      

      Program Output

      As you can see below, removal of the files succeeds, but attempts to remove the directories fail, and both directories remain on the QFileSystemWatcher::directories() list.

      Removing 1 Directories
      Remove fail - G:/TestFiles/Test1
      
      Removing 3 Files
      Files removed successfully
      
      Current Watch List
      G:/TestFiles/Test1
      
      Removing 2 Directories
      Remove fail - G:/TestFiles/Test1
      Remove fail - G:/TestFiles/Test2
      
      Removing 3 Files
      Files removed successfully
      
      Current Watch List
      G:/TestFiles/Test1
      G:/TestFiles/Test2
      

      2) QFileSystemWatcher::removePath() Fails to Remove Files

      Steps to recreate:

      • Add some files from a directory to a QFileSystemWatcher with addPath() or addPaths().
      • Add the directory in which the files are contained to the same QFileSystemWatcher with addPath().
      • Try to remove the files with removePath() or removePaths() - the operation will fail every time.
      • The directory can be successfully removed, but even after removing the directory, attempts to remove files will fail.
      • The files will still be present on the list returned by files(), but no fileChanged() signal will be emitted if a file is modified.
      • If you try to re-add the files with addPath() or addPaths(), the operation will fail because the files are already on the watch list.  This means that there's no way to get fileChanged() signals working again after a failed attempt to remove a file from the QFileSystemWatcher.

      Sample Program to Illustrate Problem

      Same program as above, but with the below main() function.  The only difference is that the order of adding the items has been changed so that the files are added first, then the directory.

      int main(int argc, char *argv[])
      {
          QCoreApplication a(argc, argv);
      
          QFileSystemWatcher fsWatcher;
      
          fsWatcher.addPath("G:/TestFiles/Test1/File 1-1.txt");
          fsWatcher.addPath("G:/TestFiles/Test1/File 1-2.txt");
          fsWatcher.addPath("G:/TestFiles/Test1/File 1-3.txt");
          fsWatcher.addPath("G:/TestFiles/Test1");
      
          RemoveItems(fsWatcher, "Directories", fsWatcher.directories());
          RemoveItems(fsWatcher, "Files", fsWatcher.files());
      
          PrintLists(fsWatcher.directories(), fsWatcher.files());
      
          fsWatcher.addPath("G:/TestFiles/Test2/File 2-1.txt");
          fsWatcher.addPath("G:/TestFiles/Test2/File 2-2.txt");
          fsWatcher.addPath("G:/TestFiles/Test2/File 2-3.txt");
          fsWatcher.addPath("G:/TestFiles/Test2");
      
          RemoveItems(fsWatcher, "Directories", fsWatcher.directories());
          RemoveItems(fsWatcher, "Files", fsWatcher.files());
      
          PrintLists(fsWatcher.directories(), fsWatcher.files());
      
          return a.exec();
      }
      

      Program Output

      As you can see below, removal of the now directories succeeds, but attempts to remove the files fail, and all six files remaining on the QFileSystemWatcher::files() list.

      Removing 1 Directories
      Directories removed successfully
      
      Removing 3 Files
      Remove fail - G:/TestFiles/Test1/File 1-1.txt
      Remove fail - G:/TestFiles/Test1/File 1-2.txt
      Remove fail - G:/TestFiles/Test1/File 1-3.txt
      
      Current Watch List
      G:/TestFiles/Test1/File 1-1.txt
      G:/TestFiles/Test1/File 1-2.txt
      G:/TestFiles/Test1/File 1-3.txt
      
      Removing 1 Directories
      Directories removed successfully
      
      Removing 6 Files
      Remove fail - G:/TestFiles/Test1/File 1-1.txt
      Remove fail - G:/TestFiles/Test1/File 1-2.txt
      Remove fail - G:/TestFiles/Test1/File 1-3.txt
      Remove fail - G:/TestFiles/Test2/File 2-1.txt
      Remove fail - G:/TestFiles/Test2/File 2-2.txt
      Remove fail - G:/TestFiles/Test2/File 2-3.txt
      
      Current Watch List
      G:/TestFiles/Test1/File 1-1.txt
      G:/TestFiles/Test1/File 1-2.txt
      G:/TestFiles/Test1/File 1-3.txt
      G:/TestFiles/Test2/File 2-1.txt
      G:/TestFiles/Test2/File 2-2.txt
      G:/TestFiles/Test2/File 2-3.txt

      Temporary Workaround

      To avoid this problem use two separate QFileSystemWatchers, one for files and the for directories.

      Sample Program To Illustrate Workaround

      Same as the first program, but with the below main() function.  The difference is it uses separate QFileSystemWatchers for files and directories.

      int main(int argc, char *argv[])
      {
          QCoreApplication a(argc, argv);
      
          QFileSystemWatcher fsWatcherDir;
          QFileSystemWatcher fsWatcherFile;
      
          fsWatcherDir.addPath("G:/TestFiles/Test1");
          fsWatcherFile.addPath("G:/TestFiles/Test1/File 1-1.txt");
          fsWatcherFile.addPath("G:/TestFiles/Test1/File 1-2.txt");
          fsWatcherFile.addPath("G:/TestFiles/Test1/File 1-3.txt");
      
          RemoveItems(fsWatcherDir, "Directories", fsWatcherDir.directories());
          RemoveItems(fsWatcherFile, "Files", fsWatcherFile.files());
      
          PrintLists(fsWatcherDir.directories(), fsWatcherFile.files());
      
          fsWatcherDir.addPath("G:/TestFiles/Test2");
          fsWatcherFile.addPath("G:/TestFiles/Test2/File 2-1.txt");
          fsWatcherFile.addPath("G:/TestFiles/Test2/File 2-2.txt");
          fsWatcherFile.addPath("G:/TestFiles/Test2/File 2-3.txt");
      
          RemoveItems(fsWatcherDir, "Directories", fsWatcherDir.directories());
          RemoveItems(fsWatcherFile, "Files", fsWatcherFile.files());
      
          PrintLists(fsWatcherDir.directories(), fsWatcherFile.files());
      
          return a.exec();
      }
      

      Program Output

      As you can see below, using separate QFileSystemWatchers for files and directories allows the program to work as intended.

      Removing 1 Directories
      Directories removed successfully
      
      Removing 3 Files
      Files removed successfully
      
      Current Watch List
      [Empty]
      
      Removing 1 Directories
      Directories removed successfully
      
      Removing 3 Files
      Files removed successfully
      
      Current Watch List
      [Empty]
      

       

        Attachments

        1. qtbug61792_diag.diff
          8 kB
        2. qtbug61792_log.txt
          12 kB
        3. qtbug61792.zip
          2 kB
        For Gerrit Dashboard: QTBUG-61792
        # Subject Branch Project Status CR V

          Activity

            People

            • Assignee:
              kleint Friedemann Kleint
              Reporter:
              inviska Richard Craig
            • Votes:
              0 Vote for this issue
              Watchers:
              4 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved:

                Gerrit Reviews

                There are no open Gerrit changes