Merge lp:~ajalkane/ubuntu-filemanager-app/sdcard-support into lp:ubuntu-filemanager-app

Proposed by Arto Jalkanen
Status: Merged
Approved by: Arto Jalkanen
Approved revision: 362
Merged at revision: 358
Proposed branch: lp:~ajalkane/ubuntu-filemanager-app/sdcard-support
Merge into: lp:ubuntu-filemanager-app
Diff against target: 753 lines (+331/-78)
11 files modified
src/app/qml/components/PlacesSidebar.qml (+0/-11)
src/app/qml/ui/FolderListPage.qml (+16/-7)
src/app/qml/ui/PlacesPage.qml (+1/-3)
src/app/qml/ui/PlacesPopover.qml (+1/-2)
src/plugin/folderlistmodel/dirmodel.cpp (+13/-39)
src/plugin/folderlistmodel/dirmodel.h (+21/-7)
src/plugin/placesmodel/CMakeLists.txt (+2/-0)
src/plugin/placesmodel/placesmodel.cpp (+82/-8)
src/plugin/placesmodel/placesmodel.h (+27/-1)
src/plugin/placesmodel/qmtabparser.cpp (+107/-0)
src/plugin/placesmodel/qmtabparser.h (+61/-0)
To merge this branch: bzr merge lp:~ajalkane/ubuntu-filemanager-app/sdcard-support
Reviewer Review Type Date Requested Status
Ubuntu Phone Apps Jenkins Bot continuous-integration Approve
Carlos Jose Mazieri Approve
Review via email: mp+245483@code.launchpad.net

Commit message

SDCard support: Places plugin watches /etc/mtab for any mounts that are done under /media/$USER directory. Whenever a directory is mounted or unmounted there, it's added or removed to the Places model so that it can be seen in the UI.

C++ backend has been reworked to allow arbitrary allowed directories, instead of hard-coded MTP directories. Allowed directories are initialized now in QML side, which gives flexibility for future changes.

Description of the change

Support for showing mounted SDCards.

Basically this branch assumes that SDCards are mounted as sub-directories under /media/$USER. If anything appears there, it will be shown in the Places model.

Regarding security this merge proposal is conservative: mounted SDCards contents are not viewable in phone/tablet unless full access is unlocked first with PIN/password.

To post a comment you must log in.
Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Carlos Jose Mazieri (carlos-mazieri) wrote :

Hi Arto,

That looks nice, however if you read /etc/mtab in PlacesModel::rescanUserMountDirectories() you can have all the mounts including /media/$USER, I remember someone talking about having all the mounts in the places. This would be a complete solution for local places, only missing network places. You can use either use your timer or use the External File System Watcher directly in /etc/mtab.

You can take a look on QTrashDir::mountedPoints() (it can be turned static) in
 src/plugin/folderlistmodel/trash/qtrashdir.cpp, it gets all the mount points because any mount point can have its own trash can.

Let me know what you think about it.

review: Needs Information
Revision history for this message
Arto Jalkanen (ajalkane) wrote :

Thanks Carlos,

using mtab seems like the more proper way to do it, although it will require a bit more work as mtab file must be parsed.

I will work more on it.

Revision history for this message
Carlos Jose Mazieri (carlos-mazieri) wrote :

Thanks Arto,

Maybe you do not need to parse /etc/mtab, you can use QTrashDir::mountedPoints() or similar and only work with the mount points, if you want/need the file system type to set icons for example then you will need to parse it. Remember that there would be some mount points not accessible to the current $USER.

Revision history for this message
Arto Jalkanen (ajalkane) wrote :

Thank you Carlos, something like this is exactly what I need. There's some potential trouble though, I'll e-mail you.

358. By Arto Jalkanen

Rewrote user mount detection to use /etc/mtab.

Revision history for this message
Arto Jalkanen (ajalkane) wrote :

Carlos, please take a look at the latest commit. I rewrote the detection to use /etc/mtab. Let me know if you see more room for improvement.

Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Carlos Jose Mazieri (carlos-mazieri) wrote :

Hello Arto,

I did a little test on it and got the following output:

Location: "/home/devubuntu"
Location: "/home/devubuntu/Documents"
Location: "/home/devubuntu/Downloads"
Location: "/home/devubuntu/"
Location: "/home/devubuntu/"
Location: "/home/devubuntu/Videos"
void PlacesModel::initNewUserMountsWatcher() Start watching mtab file for new mounts "/etc/mtab"
void PlacesModel::rescanMtab() rescanning mtab "/etc/mtab"
void PlacesModel::rescanMtab() Considering "/"
void PlacesModel::rescanMtab() Considering "/proc"
void PlacesModel::rescanMtab() Considering "/sys"
void PlacesModel::rescanMtab() Considering "/sys/fs/cgroup"
void PlacesModel::rescanMtab() Considering "/sys/fs/fuse/connections"
void PlacesModel::rescanMtab() Considering "/sys/kernel/debug"
void PlacesModel::rescanMtab() Considering "/sys/kernel/security"
void PlacesModel::rescanMtab() Considering "/dev"
void PlacesModel::rescanMtab() Considering "/dev/pts"
void PlacesModel::rescanMtab() Considering "/run"
void PlacesModel::rescanMtab() Considering "/run/lock"
void PlacesModel::rescanMtab() Considering "/run/shm"
void PlacesModel::rescanMtab() Considering "/run/user"
void PlacesModel::rescanMtab() Considering "/sys/fs/pstore"
void PlacesModel::rescanMtab() Considering "/mnt/backup"
void PlacesModel::rescanMtab() Considering "/mnt/storage"
void PlacesModel::rescanMtab() Considering "/sys/fs/cgroup/systemd"
void PlacesModel::rescanMtab() Considering "/run/vmblock-fuse"

I expected that "/" , "/mnt/backup" and "/mnt/storage" would be added into places.

I think that QMtabParser::parseEntries() should have a filter to discard virtual file systems and PlacesModel::rescanMtab() should consider any mounted file system not only mount points as /media/???.

I modified the code a little bit and got:

void PlacesModel::initNewUserMountsWatcher() Start watching mtab file for new mounts "/etc/mtab"
void PlacesModel::rescanMtab() rescanning mtab "/etc/mtab"
void PlacesModel::rescanMtab() Considering fsName: "/dev/sda1" dir: "/" type: "ext4"
void PlacesModel::rescanMtab() Adding as userMount directory dir "/"
void PlacesModel::rescanMtab() Considering fsName: "/dev/sda6" dir: "/mnt/backup" type: "ext3"
void PlacesModel::rescanMtab() Adding as userMount directory dir "/mnt/backup"
void PlacesModel::rescanMtab() Considering fsName: "/dev/sda7" dir: "/mnt/storage" type: "ext4"
void PlacesModel::rescanMtab() Adding as userMount directory dir "/mnt/storage"
void PlacesModel::rescanMtab() user mount added: "/mnt/backup"
void PlacesModel::rescanMtab() user mount added: "/mnt/storage"
void PlacesModel::rescanMtab() user mount added: "/"

Please take a look on my suggestion, it still needs some improvements, we should maybe discard "/" and get it from QStandardPaths or QDir:
https://code.launchpad.net/~carlos-mazieri/ubuntu-filemanager-app/sdcard-support

Thanks.

Revision history for this message
Arto Jalkanen (ajalkane) wrote :

Thank you Carlos for detailed testing and the improvements!

Well, this branch was meant to add SDCard support and that's why I was content to limiting it to just that - because I did not think I have good enough knowledge to include support for other mounts without more research.

But with your changes we can add more comprehensive support for other usecases at the same time. To me your changes look just great.

So how about you propose a merge of your changes to my branch, and so we get with sdcard support a whole lot more?

Regarding "/", I think it's very good that it'd come from the code you added. Right now it's hard-coded addition in QML side, after this code we can get rid of that too.

Thanks.

Revision history for this message
Carlos Jose Mazieri (carlos-mazieri) wrote :
359. By Arto Jalkanen

Improvements from Carloz Mazieri: adding also other user mounts (such as network mounts) besides SDCard and other mounts residing under /media/sdcard

360. By Arto Jalkanen

Removed "userMountPath" as it's not needed anymore with more generalized solution. Also removed from QML the special-case of path "/" as new solution handles that also.

361. By Arto Jalkanen

Trying to revert pot file.

362. By Arto Jalkanen

Added Carlos to authors.

Revision history for this message
Arto Jalkanen (ajalkane) wrote :

Merged Carlos' improvements and did other changes needed due to that. Ready for review.

Revision history for this message
Carlos Jose Mazieri (carlos-mazieri) wrote :

OK, we have all the interesting mounts to the user.

review: Approve
Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/app/qml/components/PlacesSidebar.qml'
2--- src/app/qml/components/PlacesSidebar.qml 2014-10-20 15:52:41 +0000
3+++ src/app/qml/components/PlacesSidebar.qml 2015-01-03 17:18:33 +0000
4@@ -20,7 +20,6 @@
5 import Ubuntu.Components 1.1
6 import Ubuntu.Components.ListItems 1.0
7 import Ubuntu.Components.Popups 1.0
8-import com.ubuntu.PlacesModel 0.1
9
10 Sidebar {
11 id: root
12@@ -54,16 +53,6 @@
13 text: i18n.tr("Places")
14 }
15
16- PlacesModel {
17- id: userplaces
18-
19- // By default, the model only contains the
20- // user directories. Add the file system location too
21- Component.onCompleted: {
22- addLocation("/");
23- }
24- }
25-
26 Repeater {
27 id: placesList
28 objectName: "placesList"
29
30=== modified file 'src/app/qml/ui/FolderListPage.qml'
31--- src/app/qml/ui/FolderListPage.qml 2014-12-28 06:18:40 +0000
32+++ src/app/qml/ui/FolderListPage.qml 2015-01-03 17:18:33 +0000
33@@ -21,7 +21,6 @@
34 import Ubuntu.Components.Popups 1.0
35 import Ubuntu.Components.ListItems 1.0
36 import org.nemomobile.folderlistmodel 1.0
37-import com.ubuntu.PlacesModel 0.1
38 import com.ubuntu.Archives 0.1
39 import "../components"
40 import "../upstream"
41@@ -117,7 +116,7 @@
42 id: unlockButton
43 iconName: "lock"
44 text: i18n.tr("Unlock full access")
45- visible: pageModel.onlyMTPPaths
46+ visible: pageModel.onlyAllowedPaths
47 onTriggered: {
48 console.log("Full access clicked")
49 var authDialog = PopupUtils.open(Qt.resolvedUrl("AuthenticationDialog.qml"),
50@@ -126,7 +125,7 @@
51 authDialog.passwordEntered.connect(function(password) {
52 if (pamAuthentication.validatePasswordToken(password)) {
53 console.log("Authenticated for full access")
54- pageModel.onlyMTPPaths = false
55+ pageModel.onlyAllowedPaths = false
56 } else {
57 PopupUtils.open(Qt.resolvedUrl("NotifyDialog.qml"), folderListPage,
58 {
59@@ -191,13 +190,11 @@
60 }
61 }
62
63- PlacesModel { id: userplaces }
64-
65 FolderListModel {
66 id: pageModel
67 path: folderListPage.folder
68 enableExternalFSWatcher: true
69- onlyMTPPaths: !noAuthentication && pamAuthentication.requireAuthentication()
70+ onlyAllowedPaths: !noAuthentication && pamAuthentication.requireAuthentication()
71
72 // Properties to emulate a model entry for use by FileDetailsPopover
73 property bool isDir: true
74@@ -205,6 +202,15 @@
75 property string fileSize: i18n.tr("%1 file", "%1 files", folderListView.count).arg(folderListView.count)
76 property bool isReadable: true
77 property bool isExecutable: true
78+
79+ Component.onCompleted: {
80+ // Add default allowed paths
81+ addAllowedDirectory(userplaces.locationDocuments)
82+ addAllowedDirectory(userplaces.locationDownloads)
83+ addAllowedDirectory(userplaces.locationMusic)
84+ addAllowedDirectory(userplaces.locationPictures)
85+ addAllowedDirectory(userplaces.locationVideos)
86+ }
87 }
88
89 FolderListModel {
90@@ -312,7 +318,7 @@
91 width: parent.width - sidebar.width
92
93 spacing: units.gu(2)
94- visible: fileSelectorMode || pageModel.onlyMTPPaths
95+ visible: fileSelectorMode || pageModel.onlyAllowedPaths
96
97 Button {
98 text: i18n.tr("Select")
99@@ -788,6 +794,9 @@
100 iconPath = "/usr/share/icons/Humanity/places/48/folder-videos.svg"
101 } else if (file === "/") {
102 iconPath = "/usr/share/icons/Humanity/devices/48/drive-harddisk.svg"
103+ } else if (userplaces.isUserMountDirectory(file)) {
104+ // In context of Ubuntu Touch this means SDCard currently.
105+ iconPath = "/usr/share/icons/Humanity/devices/48/drive-removable-media.svg"
106 }
107
108 return Qt.resolvedUrl(iconPath)
109
110=== modified file 'src/app/qml/ui/PlacesPage.qml'
111--- src/app/qml/ui/PlacesPage.qml 2014-11-30 10:26:19 +0000
112+++ src/app/qml/ui/PlacesPage.qml 2015-01-03 17:18:33 +0000
113@@ -20,7 +20,6 @@
114 import Ubuntu.Components.Popups 1.0
115 import Ubuntu.Components.ListItems 1.0
116 import org.nemomobile.folderlistmodel 1.0
117-import com.ubuntu.PlacesModel 0.1
118
119 Page {
120 id: root
121@@ -88,8 +87,7 @@
122 Repeater {
123 id: placesList
124 objectName: "placesList"
125-
126- model: PlacesModel {}
127+ model: userplaces
128
129 delegate: Standard {
130 objectName: "place" + folderDisplayName(path).replace(/ /g,'')
131
132=== modified file 'src/app/qml/ui/PlacesPopover.qml'
133--- src/app/qml/ui/PlacesPopover.qml 2014-10-20 21:19:26 +0000
134+++ src/app/qml/ui/PlacesPopover.qml 2015-01-03 17:18:33 +0000
135@@ -19,7 +19,6 @@
136 import Ubuntu.Components 1.1
137 import Ubuntu.Components.Popups 1.0
138 import Ubuntu.Components.ListItems 1.0
139-import com.ubuntu.PlacesModel 0.1
140
141 Popover {
142 id: root
143@@ -118,7 +117,7 @@
144 id: placesList
145 objectName: "placesList"
146 visible: true
147- model: PlacesModel {}
148+ model: userplaces
149
150 delegate: Standard {
151 visible: placesList.visible
152
153=== modified file 'src/plugin/folderlistmodel/dirmodel.cpp'
154--- src/plugin/folderlistmodel/dirmodel.cpp 2014-10-23 04:24:38 +0000
155+++ src/plugin/folderlistmodel/dirmodel.cpp 2015-01-03 17:18:33 +0000
156@@ -79,31 +79,8 @@
157
158 namespace {
159 QHash<QByteArray, int> roleMapping;
160- QList<QString> mtpDirectories;
161-
162- QList<QStandardPaths::StandardLocation> mtpStandardLocations =
163- QList<QStandardPaths::StandardLocation>()
164- << QStandardPaths::DocumentsLocation
165- << QStandardPaths::DownloadLocation
166- << QStandardPaths::MusicLocation
167- << QStandardPaths::PicturesLocation
168- << QStandardPaths::MoviesLocation;
169-
170-
171- void buildMTPDirectories() {
172- mtpDirectories.clear();
173- foreach (const QStandardPaths::StandardLocation &standardLocation, mtpStandardLocations) {
174- QStringList locations = QStandardPaths::standardLocations(standardLocation);
175-
176- foreach (const QString &location, locations) {
177- mtpDirectories << location;
178- }
179- }
180- }
181 }
182
183-
184-
185 /*!
186 * Sort was originally done in \ref onItemsAdded() and that code is now in \ref addItem(),
187 * the reason to keep doing sort and do not let QDir does it is that when adding new items
188@@ -126,7 +103,7 @@
189 , mIsRecursive(false)
190 , mReadsMediaMetadata(false)
191 , mShowHiddenFiles(false)
192- , mOnlyMTPPaths(false)
193+ , mOnlyAllowedPaths(false)
194 , mSortBy(SortByName)
195 , mSortOrder(SortAscending)
196 , mCompareFunction(0)
197@@ -496,7 +473,7 @@
198 }
199
200
201-bool DirModel::isMTPPath(const QString &absolutePath) const {
202+bool DirModel::isAllowedPath(const QString &absolutePath) const {
203 // A simple fail check to try protect against most obvious accidental usages.
204 // This is a private function and should always get an absolute FilePath from caller,
205 // but just in case check if there's relational path in there.
206@@ -506,13 +483,10 @@
207 return false;
208 }
209
210- if (mtpDirectories.isEmpty()) {
211- buildMTPDirectories();
212- }
213- foreach (const QString &mtpDirectory, mtpDirectories) {
214- if (absolutePath == mtpDirectory) return true;
215- // Returns true for any file/folder inside MTP directory
216- if (absolutePath.startsWith(mtpDirectory + "/")) return true;
217+ foreach (const QString &allowedDirectory, m_allowedDirs) {
218+ if (absolutePath == allowedDirectory) return true;
219+ // Returns true for any file/folder inside allowed directory
220+ if (absolutePath.startsWith(allowedDirectory + "/")) return true;
221 }
222
223 return false;
224@@ -523,7 +497,7 @@
225 }
226
227 bool DirModel::allowAccess(const QString &absoluteFilePath) const {
228- return !mOnlyMTPPaths || isMTPPath(absoluteFilePath);
229+ return !mOnlyAllowedPaths || isAllowedPath(absoluteFilePath);
230 }
231
232 void DirModel::onItemsAdded(const DirItemInfoList &newFiles)
233@@ -1107,19 +1081,19 @@
234 }
235 }
236
237-bool DirModel::getOnlyMTPPaths() const
238+bool DirModel::getOnlyAllowedPaths() const
239 {
240- return mOnlyMTPPaths;
241+ return mOnlyAllowedPaths;
242 }
243
244
245-void DirModel::setOnlyMTPPaths(bool onlyMTPPaths)
246+void DirModel::setOnlyAllowedPaths(bool onlyAllowedPaths)
247 {
248- if (onlyMTPPaths != mOnlyMTPPaths)
249+ if (onlyAllowedPaths != mOnlyAllowedPaths)
250 {
251- mOnlyMTPPaths = onlyMTPPaths;
252+ mOnlyAllowedPaths = onlyAllowedPaths;
253 refresh();
254- emit onlyMTPPathsChanged();
255+ emit onlyAllowedPathsChanged();
256 }
257 }
258
259
260=== modified file 'src/plugin/folderlistmodel/dirmodel.h'
261--- src/plugin/folderlistmodel/dirmodel.h 2014-10-23 04:24:38 +0000
262+++ src/plugin/folderlistmodel/dirmodel.h 2015-01-03 17:18:33 +0000
263@@ -35,6 +35,7 @@
264
265 #include <QStringList>
266 #include <QDir>
267+#include <QSet>
268
269 #include "iorequest.h"
270 #include "filecompare.h"
271@@ -179,8 +180,8 @@
272 Q_PROPERTY(bool showHiddenFiles READ getShowHiddenFiles WRITE setShowHiddenFiles NOTIFY showHiddenFilesChanged)
273 bool getShowHiddenFiles() const;
274
275- Q_PROPERTY(bool onlyMTPPaths READ getOnlyMTPPaths WRITE setOnlyMTPPaths NOTIFY onlyMTPPathsChanged)
276- bool getOnlyMTPPaths() const;
277+ Q_PROPERTY(bool onlyAllowedPaths READ getOnlyAllowedPaths WRITE setOnlyAllowedPaths NOTIFY onlyAllowedPathsChanged)
278+ bool getOnlyAllowedPaths() const;
279
280 Q_ENUMS(SortBy)
281 enum SortBy
282@@ -389,9 +390,9 @@
283 void setShowDirectories(bool showDirectories);
284 void setShowHiddenFiles(bool show);
285 /*!
286- * \brief if set to true then only MTP paths are shown or be modified
287+ * \brief if set to true then only Allowed paths are shown or be modified
288 */
289- void setOnlyMTPPaths(bool onlyMTPPaths);
290+ void setOnlyAllowedPaths(bool onlyAllowedPaths);
291 void setSortBy(SortBy field);
292 void setSortOrder(SortOrder order);
293 void setEnabledExternalFSWatcher(bool enable);
294@@ -402,6 +403,17 @@
295 void toggleSortOrder();
296 void toggleSortBy();
297
298+ /*!
299+ * \brief Adds a directory to the set of directories that are accessible when "onlyAllowedPaths" property is set.
300+ */
301+ inline void addAllowedDirectory(const QString &allowedDirAbsolutePath) {
302+ m_allowedDirs << allowedDirAbsolutePath;
303+ }
304+
305+ inline void removeAllowedDirectory(const QString &allowedDirAbsolutePath) {
306+ m_allowedDirs.remove(allowedDirAbsolutePath);
307+ }
308+
309 signals:
310 /*!
311 * \brief insertedItem()
312@@ -423,7 +435,7 @@
313 void progress(int curItem, int totalItems, int percent);
314
315 void showHiddenFilesChanged();
316- void onlyMTPPathsChanged();
317+ void onlyAllowedPathsChanged();
318 void sortByChanged();
319 void sortOrderChanged();
320 void clipboardChanged();
321@@ -464,7 +476,7 @@
322
323 private:
324 bool mShowHiddenFiles;
325- bool mOnlyMTPPaths;
326+ bool mOnlyAllowedPaths;
327 SortBy mSortBy;
328 SortOrder mSortOrder;
329 CompareFunction mCompareFunction;
330@@ -481,6 +493,8 @@
331 #ifndef DO_NOT_USE_TAG_LIB
332 QVariant getAudioMetaData(const QFileInfo& fi, int role) const;
333 #endif
334+ QSet<QString> m_allowedDirs;
335+
336 //[0]
337
338 #if defined(REGRESSION_TEST_FOLDERLISTMODEL)
339@@ -492,7 +506,7 @@
340
341 bool allowAccess(const DirItemInfo &fi) const;
342 bool allowAccess(const QString &path) const;
343- bool isMTPPath(const QString &absolutePath) const;
344+ bool isAllowedPath(const QString &absolutePath) const;
345 };
346
347
348
349=== modified file 'src/plugin/placesmodel/CMakeLists.txt'
350--- src/plugin/placesmodel/CMakeLists.txt 2014-06-21 21:59:52 +0000
351+++ src/plugin/placesmodel/CMakeLists.txt 2015-01-03 17:18:33 +0000
352@@ -9,6 +9,8 @@
353 placesmodel.h
354 placesmodel_plugin.cpp
355 placesmodel_plugin.h
356+ qmtabparser.h
357+ qmtabparser.cpp
358 )
359
360 add_library(PlacesModel MODULE
361
362=== modified file 'src/plugin/placesmodel/placesmodel.cpp'
363--- src/plugin/placesmodel/placesmodel.cpp 2014-06-21 21:59:52 +0000
364+++ src/plugin/placesmodel/placesmodel.cpp 2015-01-03 17:18:33 +0000
365@@ -14,6 +14,7 @@
366 * along with this program. If not, see <http://www.gnu.org/licenses/>.
367 *
368 * Author : David Planella <david.planella@ubuntu.com>
369+ * Arto Jalkanen <ajalkane@gmail.com>
370 */
371
372 #include "placesmodel.h"
373@@ -24,10 +25,9 @@
374 #include <QStandardPaths>
375 #include <QDebug>
376
377-PlacesModel::PlacesModel(QAbstractListModel *parent) :
378+PlacesModel::PlacesModel(QObject *parent) :
379 QAbstractListModel(parent)
380 {
381-
382 QStringList defaultLocations;
383 // Set the storage location to a path that works well
384 // with app isolation
385@@ -55,12 +55,74 @@
386 qDebug() << "Location: " << location;
387 }
388
389+ initNewUserMountsWatcher();
390+ rescanMtab();
391 }
392
393 PlacesModel::~PlacesModel() {
394
395 }
396
397+void
398+PlacesModel::initNewUserMountsWatcher() {
399+ m_newUserMountsWatcher = new QFileSystemWatcher(this);
400+
401+ qDebug() << Q_FUNC_INFO << "Start watching mtab file for new mounts" << m_mtabParser.path();
402+
403+ m_newUserMountsWatcher->addPath(m_mtabParser.path());
404+
405+ connect(m_newUserMountsWatcher, &QFileSystemWatcher::fileChanged, this, &PlacesModel::mtabChanged);
406+}
407+
408+void
409+PlacesModel::mtabChanged(const QString &path) {
410+ qDebug() << Q_FUNC_INFO << "file changed in " << path;
411+ rescanMtab();
412+ // Since old mtab file is replaced with new contents, must readd filesystem watcher
413+ m_newUserMountsWatcher->removePath(path);
414+ m_newUserMountsWatcher->addPath(path);
415+}
416+
417+void
418+PlacesModel::rescanMtab() {
419+ const QString& path = m_mtabParser.path();
420+ qDebug() << Q_FUNC_INFO << "rescanning mtab" << path;
421+
422+ QList<QMtabEntry> entries = m_mtabParser.parseEntries();
423+
424+ QSet<QString> userMounts;
425+
426+ foreach (QMtabEntry e, entries) {
427+ qDebug() << Q_FUNC_INFO << "Considering" << "fsName:" << e.fsName << "dir:" << e.dir << "type:" << e.type;
428+ QFileInfo dir(e.dir);
429+ if (dir.isReadable() && dir.isExecutable())
430+ {
431+ qDebug() << Q_FUNC_INFO << "Adding as userMount directory dir" << e.dir;
432+ userMounts << e.dir;
433+ }
434+ }
435+
436+ QSet<QString> addedMounts = QSet<QString>(userMounts).subtract(m_userMounts);
437+ QSet<QString> removedMounts = QSet<QString>(m_userMounts).subtract(userMounts);
438+
439+ m_userMounts = userMounts;
440+
441+ foreach (QString addedMount, addedMounts) {
442+ qDebug() << Q_FUNC_INFO << "user mount added: " << addedMount;
443+ addLocationWithoutStoring(addedMount);
444+ emit userMountAdded(addedMount);
445+ }
446+
447+ foreach (QString removedMount, removedMounts) {
448+ qDebug() << Q_FUNC_INFO << "user mount removed: " << removedMount;
449+ int index = m_locations.indexOf(removedMount);
450+ if (index > -1) {
451+ removeItemWithoutStoring(index);
452+ }
453+ emit userMountRemoved(removedMount);
454+ }
455+}
456+
457 QString PlacesModel::standardLocation(QStandardPaths::StandardLocation location) const
458 {
459 QStringList locations = QStandardPaths::standardLocations(location);
460@@ -132,6 +194,14 @@
461
462 void PlacesModel::removeItem(int indexToRemove)
463 {
464+ removeItemWithoutStoring(indexToRemove);
465+
466+ // Remove the location permanently
467+ m_settings->setValue("storedLocations", m_locations);
468+}
469+
470+void PlacesModel::removeItemWithoutStoring(int indexToRemove)
471+{
472
473 // Tell Qt that we're going to be changing the model
474 // There's no tree-parent, first new item will be at
475@@ -145,13 +215,18 @@
476 // it can update the UI and everything else to reflect
477 // the new state
478 endRemoveRows();
479-
480- // Remove the location permanently
481- m_settings->setValue("storedLocations", m_locations);
482 }
483
484 void PlacesModel::addLocation(const QString &location)
485 {
486+ if (addLocationWithoutStoring(location)) {
487+ // Store the location permanently
488+ m_settings->setValue("storedLocations", m_locations);
489+ }
490+}
491+
492+bool PlacesModel::addLocationWithoutStoring(const QString &location)
493+{
494 // Do not allow for duplicates
495 if (!m_locations.contains(location)) {
496 // Tell Qt that we're going to be changing the model
497@@ -167,8 +242,7 @@
498 // it can update the UI and everything else to reflect
499 // the new state
500 endInsertRows();
501-
502- // Store the location permanently
503- m_settings->setValue("storedLocations", m_locations);
504+ return true;
505 }
506+ return false;
507 }
508
509=== modified file 'src/plugin/placesmodel/placesmodel.h'
510--- src/plugin/placesmodel/placesmodel.h 2014-06-21 21:59:52 +0000
511+++ src/plugin/placesmodel/placesmodel.h 2015-01-03 17:18:33 +0000
512@@ -14,6 +14,7 @@
513 * along with this program. If not, see <http://www.gnu.org/licenses/>.
514 *
515 * Author : David Planella <david.planella@ubuntu.com>
516+ * Arto Jalkanen <arto.jalkanen@gmail.com>
517 */
518
519 #ifndef PLACESMODEL_H
520@@ -23,6 +24,11 @@
521 #include <QAbstractListModel>
522 #include <QStandardPaths>
523 #include <QSettings>
524+#include <QFileSystemWatcher>
525+#include <QTimer>
526+#include <QSet>
527+
528+#include "qmtabparser.h"
529
530 class PlacesModel : public QAbstractListModel
531 {
532@@ -36,7 +42,7 @@
533 Q_PROPERTY(QString locationVideos READ locationVideos CONSTANT)
534
535 public:
536- explicit PlacesModel(QAbstractListModel *parent = 0);
537+ explicit PlacesModel(QObject *parent = 0);
538 ~PlacesModel();
539 QString locationHome() const;
540 QString locationDocuments() const;
541@@ -48,14 +54,34 @@
542 QVariant data(const QModelIndex &index, int role) const override;
543 QHash<int, QByteArray> roleNames() const override;
544
545+signals:
546+ void userMountAdded(const QString &path);
547+ void userMountRemoved(const QString &paht);
548+
549 public slots:
550 void addLocation(const QString &location);
551 void removeItem(int indexToRemove);
552+ inline bool isUserMountDirectory(const QString location) {
553+ return m_userMounts.contains(location);
554+ }
555+
556+private slots:
557+ void mtabChanged(const QString &path);
558+ void rescanMtab();
559
560 private:
561+ void initNewUserMountsWatcher();
562+ // Returns true if location was not known before, and false if it was known
563+ bool addLocationWithoutStoring(const QString &location);
564+ // Returns true if location was not known before, and false if it was known
565+ void removeItemWithoutStoring(int itemToRemove);
566+
567+ QMtabParser m_mtabParser;
568 QString standardLocation(QStandardPaths::StandardLocation location) const;
569 QStringList m_locations;
570 QSettings *m_settings;
571+ QFileSystemWatcher *m_newUserMountsWatcher;
572+ QSet<QString> m_userMounts;
573 };
574
575 #endif // PLACESMODEL_H
576
577=== added file 'src/plugin/placesmodel/qmtabparser.cpp'
578--- src/plugin/placesmodel/qmtabparser.cpp 1970-01-01 00:00:00 +0000
579+++ src/plugin/placesmodel/qmtabparser.cpp 2015-01-03 17:18:33 +0000
580@@ -0,0 +1,107 @@
581+/*
582+ * Copyright (C) 2015 Canonical Ltd
583+ *
584+ * This program is free software: you can redistribute it and/or modify
585+ * it under the terms of the GNU General Public License version 3 as
586+ * published by the Free Software Foundation.
587+ *
588+ * This program is distributed in the hope that it will be useful,
589+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
590+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
591+ * GNU General Public License for more details.
592+ *
593+ * You should have received a copy of the GNU General Public License
594+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
595+ *
596+ * Author : Arto Jalkanen <ajalkane@gmail.com>
597+ * Carlos J Mazieri <carlos.mazieri@gmail.com>
598+ */
599+
600+#include <qmtabparser.h>
601+
602+#include <mntent.h>
603+
604+#include <QFileInfo>
605+#include <QStringList>
606+
607+class MtabFileGuard {
608+ FILE *mtabFile;
609+
610+public:
611+ MtabFileGuard(FILE *f) {
612+ mtabFile = f;
613+ }
614+ ~MtabFileGuard() {
615+ endmntent(mtabFile);
616+ }
617+};
618+
619+QMtabParser::QMtabParser(const QString& path, QObject *parent)
620+ : QObject(parent) {
621+ m_path = path.isEmpty() ? _PATH_MOUNTED : path;
622+}
623+
624+QMtabParser::~QMtabParser() {}
625+
626+QList<QMtabEntry>
627+QMtabParser::parseEntries() {
628+ QList<QMtabEntry> entries;
629+
630+ FILE *f = setmntent(m_path.toLocal8Bit().data(), "r");
631+ if (f == 0) {
632+ return entries;
633+ }
634+
635+ MtabFileGuard guard(f);
636+
637+ struct mntent entStorage;
638+ char buffer[1024];
639+ while (mntent *ent = getmntent_r(f, &entStorage, buffer, 1024)) {
640+ QMtabEntry entry;
641+ entry.fsName = ent->mnt_fsname;
642+ entry.dir = ent->mnt_dir;
643+ entry.type = ent->mnt_type;
644+ entry.opts = ent->mnt_opts;
645+ entry.freq = ent->mnt_freq;
646+ entry.passno = ent->mnt_passno;
647+ if (fsHasUserContent(entry)) {
648+ entries << entry;
649+ }
650+ }
651+
652+ return entries;
653+}
654+
655+
656+bool QMtabParser::fsHasUserContent(const QMtabEntry &fs)
657+{
658+ //check for disk file systems like /dev/sda?
659+ bool ret = QFileInfo(fs.fsName).exists();
660+ if (!ret)
661+ {
662+ /*!
663+ * \link http://en.wikipedia.org/wiki/List_of_file_systems#Distributed_file_systems
664+ */
665+ static QStringList netFs = QStringList()
666+ << "v9fs"
667+ << "afs"
668+ << "nfs"
669+ << "smb"
670+ << "afp"
671+ << "dce"
672+ << "dfs"
673+ << "fal"
674+ << "sfs"
675+ << "ncp"
676+ << "dfs"
677+ << "cfs"
678+ << "coda"
679+ << "MooseFS"
680+ << "ssh"
681+ << "sftp"
682+ << "sshfs"
683+ ;
684+ ret = netFs.contains(fs.type, Qt::CaseSensitive);
685+ }
686+ return ret;
687+}
688
689=== added file 'src/plugin/placesmodel/qmtabparser.h'
690--- src/plugin/placesmodel/qmtabparser.h 1970-01-01 00:00:00 +0000
691+++ src/plugin/placesmodel/qmtabparser.h 2015-01-03 17:18:33 +0000
692@@ -0,0 +1,61 @@
693+/*
694+ * Copyright (C) 2015 Canonical Ltd
695+ *
696+ * This program is free software: you can redistribute it and/or modify
697+ * it under the terms of the GNU General Public License version 3 as
698+ * published by the Free Software Foundation.
699+ *
700+ * This program is distributed in the hope that it will be useful,
701+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
702+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
703+ * GNU General Public License for more details.
704+ *
705+ * You should have received a copy of the GNU General Public License
706+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
707+ *
708+ * Author : Arto Jalkanen <ajalkane@gmail.com>
709+ * Carlos J Mazieri <carlos.mazieri@gmail.com>
710+ */
711+
712+#ifndef QMTABPARSER_H
713+#define QMTABPARSER_H
714+
715+#include <QObject>
716+
717+struct QMtabEntry {
718+ QString fsName;
719+ QString dir;
720+ QString type;
721+ QString opts;
722+ int freq;
723+ int passno;
724+};
725+
726+class QMtabParser : public QObject
727+{
728+ Q_OBJECT
729+ QString m_path;
730+
731+public:
732+ explicit QMtabParser(const QString& path = QString(), QObject *parent = 0);
733+ ~QMtabParser();
734+
735+ QList<QMtabEntry> parseEntries();
736+
737+ inline const QString& path() { return m_path; }
738+
739+private:
740+ /*!
741+ * \brief fsHasUserContent() consider Disk and Network file systems as valid
742+ * \param fs
743+ * \return TRUE if the filesystem is considered as having normal user files, FALSE when it is supposed to be system FS
744+ *
745+ * \sa \link http://en.wikipedia.org/wiki/List_of_file_systems
746+ */
747+ bool fsHasUserContent(const struct QMtabEntry& fs);
748+};
749+
750+#endif // QMTABPARSER_H
751+
752+
753+

Subscribers

People subscribed via source and target branches