[kde-doc-english] [juk] /: folder-scan: Support a list of folders to ignore when auto-scanning.

Michael Pyne mpyne at kde.org
Sun May 26 02:10:22 UTC 2013


Git commit 2f18ba1e02bd5ef10cec2ab7d9a1e0f96c70e763 by Michael Pyne, on behalf of Tom Li.
Committed on 12/05/2013 at 12:15.
Pushed by mpyne into branch 'master'.

folder-scan: Support a list of folders to ignore when auto-scanning.

Feature initially authored by Tom Li (see the Review Request and bug for
more details).

The idea is that the Manage Folders dialog now will have a list of
folders that should not be automatically searched (which would override
the list of folders to scan on startup). This way you can avoid having
to completely change up your music layout on-disk just to keep JuK from
automatically grabbing songs you don't want or need managed from within
JuK.

I ended up having to rework a bit to fix some corner cases (such as the
directory watcher which might flag new files under an excluded directory
after JuK startup), and avoid accidentally removing the ability to
manually select files under these excluded directories for addition to
the collection.

If a user does want to remove files that are already in the collection
list, they can enable the "File Name (Full Path)" column and then use
the search bar to find the offending path name, and select and remove
the files that are shown.

Note for posterity that the "Fixed In" version specifies the next
KDE-wide Software Compilation release, not the internal JuK version.

Thanks again to Tom for drafting the initial patch and being
extraordinarily patient while I tried to find time to review, re-review,
and make the finishing touches.

GUI:
REVIEW:110273
FEATURE:319106
FIXED-IN:4.11

M  +45   -0    directorylist.cpp
M  +7    -1    directorylist.h
M  +91   -8    directorylistbase.ui
M  +10   -5    playlist.cpp
M  +64   -9    playlistcollection.cpp
M  +8    -0    playlistcollection.h

http://commits.kde.org/juk/2f18ba1e02bd5ef10cec2ab7d9a1e0f96c70e763

diff --git a/directorylist.cpp b/directorylist.cpp
index b715a2c..d584e68 100644
--- a/directorylist.cpp
+++ b/directorylist.cpp
@@ -32,6 +32,7 @@
 ////////////////////////////////////////////////////////////////////////////////
 
 DirectoryList::DirectoryList(QStringList directories,
+                             QStringList excludedDirectories,
                              bool importPlaylists,
                              QWidget *parent) :
     KDialog(parent),
@@ -44,6 +45,7 @@ DirectoryList::DirectoryList(QStringList directories,
     }
 
     m_dirListModel = new QStringListModel(directories, this);
+    m_excludedDirListModel = new QStringListModel(excludedDirectories, this);
 
     setCaption(i18n("Folder List"));
     setModal(true);
@@ -58,8 +60,13 @@ DirectoryList::DirectoryList(QStringList directories,
         SLOT(slotAddDirectory()));
     connect(m_base->removeDirectoryButton, SIGNAL(clicked()),
         SLOT(slotRemoveDirectory()));
+    connect(m_base->addExcludeDirectoryButton, SIGNAL(clicked()),
+        SLOT(slotAddExcludeDirectory()));
+    connect(m_base->removeExcludeDirectoryButton, SIGNAL(clicked()),
+        SLOT(slotRemoveExcludeDirectory()));
 
     m_base->directoryListView->setModel(m_dirListModel);
+    m_base->excludeDirectoryListView->setModel(m_excludedDirListModel);
     m_base->importPlaylistsCheckBox->setChecked(importPlaylists);
 
     resize(QSize(440, 280).expandedTo(minimumSizeHint()));
@@ -132,6 +139,44 @@ void DirectoryList::slotRemoveDirectory()
     }
 }
 
+void DirectoryList::slotAddExcludeDirectory()
+{
+    QString dir = KFileDialog::getExistingDirectory();
+
+    if(dir.isEmpty())
+        return;
+
+    QStringList dirs = m_excludedDirListModel->stringList();
+    if(!dirs.contains(dir)) {
+        dirs.append(dir);
+        m_excludedDirListModel->setStringList(dirs);
+    }
+    m_result.excludedDirs = m_excludedDirListModel->stringList();
+}
+
+void DirectoryList::slotRemoveExcludeDirectory()
+{
+    QItemSelectionModel *itemSelection = m_base->excludeDirectoryListView->selectionModel();
+
+    // These will be used in the loop below
+    QModelIndexList indexes;
+    QModelIndex firstIndex;
+    QString dir;
+
+    // The multiple indexes that are possibly present cannot be deleted one
+    // after the other, as changing the layout of the model can change the
+    // indexes (similar to iterators and container remove methods).  So, just
+    // loop deleting the first index until there is no selection.
+
+    while(itemSelection->hasSelection()) {
+        indexes = itemSelection->selectedIndexes();
+        firstIndex = indexes.first();
+
+        m_excludedDirListModel->removeRow(firstIndex.row());
+    }
+    m_result.excludedDirs = m_excludedDirListModel->stringList();
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // private methods
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/directorylist.h b/directorylist.h
index f13756f..57b4d94 100644
--- a/directorylist.h
+++ b/directorylist.h
@@ -39,11 +39,12 @@ public:
     {
         QStringList addedDirs;
         QStringList removedDirs;
+        QStringList excludedDirs;
         DialogCode status;
         bool addPlaylists;
     };
 
-    DirectoryList(QStringList directories, bool importPlaylists,
+    DirectoryList(QStringList directories, QStringList excludeDirectories, bool importPlaylists,
                   QWidget *parent = 0);
     virtual ~DirectoryList();
 
@@ -53,15 +54,20 @@ public slots:
 signals:
     void signalDirectoryAdded(const QString &directory);
     void signalDirectoryRemoved(const QString &directory);
+    void signalExcludeDirectoryAdded(const QString &directory);
+    void signalExcludeDirectoryRemoved(const QString &directory);
 
 private slots:
     void slotAddDirectory();
     void slotRemoveDirectory();
+    void slotAddExcludeDirectory();
+    void slotRemoveExcludeDirectory();
 
 private:
     static QStringList defaultFolders();
 
     QStringListModel *m_dirListModel;
+    QStringListModel *m_excludedDirListModel;
     bool m_importPlaylists;
     DirectoryListBase *m_base;
     Result m_result;
diff --git a/directorylistbase.ui b/directorylistbase.ui
index 6146726..9827f72 100644
--- a/directorylistbase.ui
+++ b/directorylistbase.ui
@@ -6,13 +6,13 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>480</width>
-    <height>271</height>
+    <width>440</width>
+    <height>332</height>
    </rect>
   </property>
-  <layout class="QHBoxLayout" name="horizontalLayout_2">
+  <layout class="QVBoxLayout" name="verticalLayout_5">
    <item>
-    <layout class="QVBoxLayout" name="verticalLayout_2">
+    <layout class="QVBoxLayout" name="verticalLayout_3">
      <item>
       <widget class="QLabel" name="informationLabel">
        <property name="text">
@@ -92,18 +92,101 @@
        </item>
       </layout>
      </item>
+    </layout>
+   </item>
+   <item>
+    <layout class="QVBoxLayout" name="verticalLayout_4">
      <item>
-      <widget class="QCheckBox" name="importPlaylistsCheckBox">
+      <widget class="QLabel" name="ExcludeInformationLabel">
        <property name="text">
-        <string>Import playlists</string>
+        <string>Please choose the folders that should be excluded from music search:</string>
        </property>
-       <property name="checked">
-        <bool>true</bool>
+       <property name="textFormat">
+        <enum>Qt::PlainText</enum>
+       </property>
+       <property name="alignment">
+        <set>Qt::AlignVCenter</set>
        </property>
       </widget>
      </item>
+     <item>
+      <layout class="QHBoxLayout" name="horizontalLayout_2">
+       <item>
+        <widget class="QListView" name="excludeDirectoryListView">
+         <property name="toolTip">
+          <string>These folders will be scanned on startup for new files.</string>
+         </property>
+         <property name="horizontalScrollBarPolicy">
+          <enum>Qt::ScrollBarAlwaysOff</enum>
+         </property>
+         <property name="editTriggers">
+          <set>QAbstractItemView::NoEditTriggers</set>
+         </property>
+         <property name="alternatingRowColors">
+          <bool>true</bool>
+         </property>
+         <property name="selectionMode">
+          <enum>QAbstractItemView::MultiSelection</enum>
+         </property>
+         <property name="textElideMode">
+          <enum>Qt::ElideMiddle</enum>
+         </property>
+         <property name="layoutMode">
+          <enum>QListView::Batched</enum>
+         </property>
+         <property name="uniformItemSizes">
+          <bool>true</bool>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <layout class="QVBoxLayout" name="verticalLayout_2">
+         <item>
+          <widget class="KPushButton" name="addExcludeDirectoryButton">
+           <property name="text">
+            <string>Add Folder...</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="KPushButton" name="removeExcludeDirectoryButton">
+           <property name="text">
+            <string>Remove Folder</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <spacer name="rightExcludeColumnSpacer">
+           <property name="orientation">
+            <enum>Qt::Vertical</enum>
+           </property>
+           <property name="sizeType">
+            <enum>QSizePolicy::Expanding</enum>
+           </property>
+           <property name="sizeHint" stdset="0">
+            <size>
+             <width>158</width>
+             <height>48</height>
+            </size>
+           </property>
+          </spacer>
+         </item>
+        </layout>
+       </item>
+      </layout>
+     </item>
     </layout>
    </item>
+   <item>
+    <widget class="QCheckBox" name="importPlaylistsCheckBox">
+     <property name="text">
+      <string>Import playlists</string>
+     </property>
+     <property name="checked">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
   </layout>
  </widget>
  <customwidgets>
diff --git a/playlist.cpp b/playlist.cpp
index 2153f9b..ecc0553 100644
--- a/playlist.cpp
+++ b/playlist.cpp
@@ -1875,32 +1875,37 @@ void Playlist::addFile(const QString &file, FileHandleList &files, bool importPl
         return;
     }
 
-
-    const QFileInfo fileInfo = QDir::cleanPath(file);
+    const QFileInfo fileInfo(QDir::cleanPath(file));
     if(!fileInfo.exists())
         return;
 
+    const QString canonicalPath = fileInfo.canonicalFilePath();
+
     if(fileInfo.isFile() && fileInfo.isReadable()) {
         if(MediaFiles::isMediaFile(file)) {
-            FileHandle f(fileInfo, fileInfo.absoluteFilePath());
+            FileHandle f(fileInfo, canonicalPath);
             f.tag();
             files.append(f);
         }
     }
 
     if(importPlaylists && MediaFiles::isPlaylistFile(file) &&
-       !m_collection->containsPlaylistFile(fileInfo.canonicalFilePath()))
+       !m_collection->containsPlaylistFile(canonicalPath))
     {
         new Playlist(m_collection, fileInfo);
         return;
     }
 
     if(fileInfo.isDir()) {
+        foreach(const QString &directory, m_collection->excludedFolders()) {
+            if(canonicalPath.startsWith(directory))
+                return; // Exclude it
+        }
 
         // Resorting to the POSIX API because QDir::listEntries() stats every
         // file and blocks while it's doing so.
 
-        DIR *dir = ::opendir(QFile::encodeName(fileInfo.filePath()));
+        DIR *dir = ::opendir(QFile::encodeName(canonicalPath));
 
         if(dir) {
             struct dirent *dirEntry;
diff --git a/playlistcollection.cpp b/playlistcollection.cpp
index 75c071d..46500ed 100644
--- a/playlistcollection.cpp
+++ b/playlistcollection.cpp
@@ -29,12 +29,14 @@
 #include <ktoggleaction.h>
 #include <kactionmenu.h>
 #include <kconfiggroup.h>
+#include <kfileitem.h>
 
 #include <config-juk.h>
 
 #include <QObject>
 #include <QPixmap>
 #include <QStackedWidget>
+#include <QMutableListIterator>
 
 #include <sys/types.h>
 #include <dirent.h>
@@ -64,6 +66,20 @@ using namespace ActionCollection;
 
 PlaylistCollection *PlaylistCollection::m_instance = 0;
 
+// Returns all folders in input list with their canonical path, if available, or
+// unchanged if not.
+static QStringList canonicalizeFolderPaths(const QStringList &folders)
+{
+    QStringList result;
+
+    foreach(const QString &folder, folders) {
+        QString canonicalFolder = QDir(folder).canonicalPath();
+        result << (!canonicalFolder.isEmpty() ? canonicalFolder : folder);
+    }
+
+    return result;
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // public methods
 ////////////////////////////////////////////////////////////////////////////////
@@ -338,8 +354,9 @@ void PlaylistCollection::open(const QStringList &l)
     {
         CollectionList::instance()->addFiles(files);
     }
-    else
+    else {
         visiblePlaylist()->addFiles(files);
+    }
 
     dataChanged();
 }
@@ -354,7 +371,7 @@ void PlaylistCollection::open(const QString &playlist, const QStringList &files)
 
 void PlaylistCollection::addFolder()
 {
-    DirectoryList l(m_folderList, m_importPlaylists, JuK::JuKInstance());
+    DirectoryList l(m_folderList, m_excludedFolderList, m_importPlaylists, JuK::JuKInstance());
     DirectoryList::Result result = l.exec();
 
     if(result.status == QDialog::Accepted) {
@@ -362,7 +379,9 @@ void PlaylistCollection::addFolder()
         m_dirLister.blockSignals(true);
 
         const bool reload = m_importPlaylists != result.addPlaylists;
+
         m_importPlaylists = result.addPlaylists;
+        m_excludedFolderList = canonicalizeFolderPaths(result.excludedDirs);
 
         foreach(const QString &dir, result.addedDirs) {
             m_dirLister.openUrl(KUrl::fromPath(dir), KDirLister::Keep);
@@ -374,10 +393,12 @@ void PlaylistCollection::addFolder()
             m_folderList.removeAll(dir);
         }
 
-        if(reload)
+        if(reload) {
             open(m_folderList);
-        else if(!result.addedDirs.isEmpty())
+        }
+        else if(!result.addedDirs.isEmpty()) {
             open(result.addedDirs);
+        }
 
         saveConfig();
 
@@ -792,7 +813,16 @@ void PlaylistCollection::removeFileFromDict(const QString &file)
 
 void PlaylistCollection::dirChanged(const QString &path)
 {
-    CollectionList::instance()->addFiles(QStringList(path));
+    QString canonicalPath = QDir(path).canonicalPath();
+    if(canonicalPath.isEmpty())
+        return;
+
+    foreach(const QString &excludedFolder, m_excludedFolderList) {
+        if(canonicalPath.startsWith(excludedFolder))
+            return;
+    }
+
+    CollectionList::instance()->addFiles(QStringList(canonicalPath));
 }
 
 Playlist *PlaylistCollection::playlistByName(const QString &name) const
@@ -808,7 +838,28 @@ Playlist *PlaylistCollection::playlistByName(const QString &name) const
 
 void PlaylistCollection::newItems(const KFileItemList &list) const
 {
-    CollectionList::instance()->slotNewItems(list);
+    // Make fast-path for the normal case
+    if(m_excludedFolderList.isEmpty()) {
+        CollectionList::instance()->slotNewItems(list);
+        return;
+    }
+
+    // Slow case: Directories to exclude from consideration
+
+    KFileItemList filteredList(list);
+
+    foreach(const QString &excludedFolder, m_excludedFolderList) {
+        QMutableListIterator<KFileItem> filteredListIterator(filteredList);
+
+        while(filteredListIterator.hasNext()) {
+            const KFileItem fileItem = filteredListIterator.next();
+
+            if(fileItem.url().path().startsWith(excludedFolder))
+                filteredListIterator.remove();
+        }
+    }
+
+    CollectionList::instance()->slotNewItems(filteredList);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -819,11 +870,14 @@ void PlaylistCollection::readConfig()
 {
     KConfigGroup config(KGlobal::config(), "Playlists");
 
-    m_importPlaylists  = config.readEntry("ImportPlaylists", true);
-    m_folderList       = config.readEntry("DirectoryList", QStringList());
+    m_importPlaylists    = config.readEntry("ImportPlaylists", true);
+    m_folderList         = config.readEntry("DirectoryList", QStringList());
+    m_excludedFolderList = canonicalizeFolderPaths(
+            config.readEntry("ExcludeDirectoryList", QStringList()));
 
-    foreach(const QString &folder, m_folderList)
+    foreach(const QString &folder, m_folderList) {
         m_dirLister.openUrl(folder, KDirLister::Keep);
+    }
 }
 
 void PlaylistCollection::saveConfig()
@@ -832,6 +886,7 @@ void PlaylistCollection::saveConfig()
     config.writeEntry("ImportPlaylists", m_importPlaylists);
     config.writeEntry("showUpcoming", action("showUpcoming")->isChecked());
     config.writePathEntry("DirectoryList", m_folderList);
+    config.writePathEntry("ExcludeDirectoryList", m_excludedFolderList);
 
     config.sync();
 }
diff --git a/playlistcollection.h b/playlistcollection.h
index d9fd9ff..62038b5 100644
--- a/playlistcollection.h
+++ b/playlistcollection.h
@@ -161,6 +161,13 @@ public:
     bool containsPlaylistFile(const QString &file) const;
 
     /**
+     * @return list of folders to exclude from automatic searching (whether
+     * by directory-change watchers or the startup folder scan). The user should
+     * still be able to manually add files even under an excluded folder.
+     */
+    QStringList excludedFolders() const { return m_excludedFolderList; }
+
+    /**
      * This is used to put up a temporary widget over the top of the playlist
      * stack.  This is part of a trick to significantly speed up painting by
      * hiding the playlist to which items are being added.
@@ -203,6 +210,7 @@ private:
     StringHash  m_playlistNames;
     StringHash  m_playlistFiles;
     QStringList m_folderList;
+    QStringList m_excludedFolderList;
     bool        m_importPlaylists;
     bool        m_searchEnabled;
     bool        m_playing;



More information about the kde-doc-english mailing list