Merge lp:~ajalkane/ubuntu-filemanager-app/sdcard-support into lp:ubuntu-filemanager-app
- sdcard-support
- Merge into trunk
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 |
Related bugs: |
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.
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote : | # |
Carlos Jose Mazieri (carlos-mazieri) wrote : | # |
Hi Arto,
That looks nice, however if you read /etc/mtab in PlacesModel:
You can take a look on QTrashDir:
src/plugin/
Let me know what you think about it.
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.
Carlos Jose Mazieri (carlos-mazieri) wrote : | # |
Thanks Arto,
Maybe you do not need to parse /etc/mtab, you can use QTrashDir:
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.
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.
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote : | # |
PASSED: Continuous integration, rev:358
http://
Executed test runs:
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
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/
Location: "/home/
Location: "/home/devubuntu/"
Location: "/home/devubuntu/"
Location: "/home/
void PlacesModel:
void PlacesModel:
void PlacesModel:
void PlacesModel:
void PlacesModel:
void PlacesModel:
void PlacesModel:
void PlacesModel:
void PlacesModel:
void PlacesModel:
void PlacesModel:
void PlacesModel:
void PlacesModel:
void PlacesModel:
void PlacesModel:
void PlacesModel:
void PlacesModel:
void PlacesModel:
void PlacesModel:
void PlacesModel:
I expected that "/" , "/mnt/backup" and "/mnt/storage" would be added into places.
I think that QMtabParser:
I modified the code a little bit and got:
void PlacesModel:
void PlacesModel:
void PlacesModel:
void PlacesModel:
void PlacesModel:
void PlacesModel:
void PlacesModel:
void PlacesModel:
void PlacesModel:
void PlacesModel:
void PlacesModel:
Please take a look on my suggestion, it still needs some improvements, we should maybe discard "/" and get it from QStandardPaths or QDir:
https:/
Thanks.
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.
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.
Arto Jalkanen (ajalkane) wrote : | # |
Merged Carlos' improvements and did other changes needed due to that. Ready for review.
Carlos Jose Mazieri (carlos-mazieri) wrote : | # |
OK, we have all the interesting mounts to the user.
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote : | # |
PASSED: Continuous integration, rev:361
http://
Executed test runs:
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote : | # |
PASSED: Continuous integration, rev:362
http://
Executed test runs:
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote : | # |
PASSED: Continuous integration, rev:362
http://
Executed test runs:
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Preview Diff
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 | + |
PASSED: Continuous integration, rev:357 91.189. 93.70:8080/ job/ubuntu- filemanager- app-ci/ 442/ 91.189. 93.70:8080/ job/generic- mediumtests- vivid/538 91.189. 93.70:8080/ job/generic- mediumtests- vivid/538/ artifact/ work/output/ *zip*/output. zip 91.189. 93.70:8080/ job/ubuntu- filemanager- app-vivid- amd64-ci/ 64
http://
Executed test runs:
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild: 91.189. 93.70:8080/ job/ubuntu- filemanager- app-ci/ 442/rebuild
http://