Merge lp:~fboucault/camera-app/async_lib_loading into lp:camera-app
- async_lib_loading
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Bill Filler |
Approved revision: | 485 |
Merged at revision: | 473 |
Proposed branch: | lp:~fboucault/camera-app/async_lib_loading |
Merge into: | lp:camera-app |
Diff against target: |
658 lines (+233/-109) 10 files modified
CameraApp/foldersmodel.cpp (+113/-61) CameraApp/foldersmodel.h (+19/-3) GalleryView.qml (+39/-1) GalleryViewLoader.qml (+5/-0) PhotogridView.qml (+1/-6) SlideshowView.qml (+6/-0) ViewFinderView.qml (+11/-10) camera-app.qml (+8/-2) po/camera-app.pot (+26/-22) tests/autopilot/camera_app/tests/test_gallery_view.py (+5/-4) |
To merge this branch: | bzr merge lp:~fboucault/camera-app/async_lib_loading |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot | continuous-integration | Needs Fixing | |
Ubuntu Phablet Team | Pending | ||
Review via email: mp+247485@code.launchpad.net |
Commit message
Asynchronously scan the filesystem for media. Fixes freeze at startup with a huge amount of media.
Significantly faster deletion when deleting a huge amount of media.
Taking a photo is now always fast even with a huge amount of media.
Description of the change
- 474. By Florian Boucault
-
Faster scanning.
- 475. By Florian Boucault
-
Sorting is still important.
- 476. By Florian Boucault
-
Reverted pointless change
- 477. By Florian Boucault
-
Do not scan until FoldersModel is fully instantiated.
- 478. By Florian Boucault
-
Clear model immediately when updating.
PS Jenkins bot (ps-jenkins) wrote : | # |
- 479. By Florian Boucault
-
Vastly more efficient selectAll with large collections.
- 480. By Florian Boucault
-
Fixed deletion bug.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:480
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
UNSTABLE: http://
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
- 481. By Florian Boucault
-
Do not monitor for added file anylonger, instead manually prepend new media files shot. Depends on fix in qtubuntu-camera https:/
/code.launchpad .net/~fboucault /qtubuntu- camera/ fix_video_ location/ +merge/ 247507 - 482. By Florian Boucault
-
Significantly faster deletion of selected files.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:482
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
UNSTABLE: http://
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
- 483. By Florian Boucault
-
Added feedback while media scanning is in progress.
- 484. By Florian Boucault
-
Updated pot file.
- 485. By Florian Boucault
-
Fixed autopilot tests.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:484
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
FAILURE: http://
FAILURE: http://
UNSTABLE: http://
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:485
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
UNSTABLE: http://
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
Preview Diff
1 | === modified file 'CameraApp/foldersmodel.cpp' | |||
2 | --- CameraApp/foldersmodel.cpp 2014-11-26 14:45:34 +0000 | |||
3 | +++ CameraApp/foldersmodel.cpp 2015-01-24 18:49:40 +0000 | |||
4 | @@ -18,14 +18,26 @@ | |||
5 | 18 | #include <QtCore/QDir> | 18 | #include <QtCore/QDir> |
6 | 19 | #include <QtCore/QUrl> | 19 | #include <QtCore/QUrl> |
7 | 20 | #include <QtCore/QDateTime> | 20 | #include <QtCore/QDateTime> |
8 | 21 | #include <QtCore/QFuture> | ||
9 | 22 | #include <QtCore/QFutureWatcher> | ||
10 | 23 | #include <QtCore/QtAlgorithms> | ||
11 | 24 | #include <QtConcurrent/QtConcurrentRun> | ||
12 | 25 | |||
13 | 26 | bool newerThan(const QFileInfo& fileInfo1, const QFileInfo& fileInfo2) | ||
14 | 27 | { | ||
15 | 28 | return fileInfo1.lastModified() > fileInfo2.lastModified(); | ||
16 | 29 | } | ||
17 | 30 | |||
18 | 21 | 31 | ||
19 | 22 | FoldersModel::FoldersModel(QObject *parent) : | 32 | FoldersModel::FoldersModel(QObject *parent) : |
20 | 23 | QAbstractListModel(parent), | 33 | QAbstractListModel(parent), |
22 | 24 | m_singleSelectionOnly(true) | 34 | m_singleSelectionOnly(true), |
23 | 35 | m_completed(false), | ||
24 | 36 | m_loading(false) | ||
25 | 25 | { | 37 | { |
26 | 26 | m_watcher = new QFileSystemWatcher(this); | 38 | m_watcher = new QFileSystemWatcher(this); |
27 | 27 | connect(m_watcher, SIGNAL(directoryChanged(QString)), this, SLOT(directoryChanged(QString))); | ||
28 | 28 | connect(m_watcher, SIGNAL(fileChanged(QString)), this, SLOT(fileChanged(QString))); | 39 | connect(m_watcher, SIGNAL(fileChanged(QString)), this, SLOT(fileChanged(QString))); |
29 | 40 | connect(&m_updateFutureWatcher, SIGNAL(finished()), this, SLOT(updateFileInfoListFinished())); | ||
30 | 29 | } | 41 | } |
31 | 30 | 42 | ||
32 | 31 | QStringList FoldersModel::folders() const | 43 | QStringList FoldersModel::folders() const |
33 | @@ -80,31 +92,70 @@ | |||
34 | 80 | return m_fileInfoList.count(); | 92 | return m_fileInfoList.count(); |
35 | 81 | } | 93 | } |
36 | 82 | 94 | ||
37 | 95 | bool FoldersModel::loading() const | ||
38 | 96 | { | ||
39 | 97 | return m_loading; | ||
40 | 98 | } | ||
41 | 99 | |||
42 | 83 | void FoldersModel::updateFileInfoList() | 100 | void FoldersModel::updateFileInfoList() |
43 | 84 | { | 101 | { |
44 | 102 | if (!m_completed) { | ||
45 | 103 | return; | ||
46 | 104 | } | ||
47 | 105 | |||
48 | 106 | m_loading = true; | ||
49 | 107 | Q_EMIT loadingChanged(); | ||
50 | 108 | |||
51 | 85 | beginResetModel(); | 109 | beginResetModel(); |
52 | 86 | m_fileInfoList.clear(); | 110 | m_fileInfoList.clear(); |
54 | 87 | Q_FOREACH (QString folder, m_folders) { | 111 | endResetModel(); |
55 | 112 | m_selectedFiles.clear(); | ||
56 | 113 | Q_EMIT selectedFilesChanged(); | ||
57 | 114 | Q_EMIT countChanged(); | ||
58 | 115 | |||
59 | 116 | m_updateFutureWatcher.cancel(); | ||
60 | 117 | QFuture<QPair<QFileInfoList, QStringList> > future = QtConcurrent::run(this, &FoldersModel::computeFileInfoList, m_folders); | ||
61 | 118 | m_updateFutureWatcher.setFuture(future); | ||
62 | 119 | } | ||
63 | 120 | |||
64 | 121 | QPair<QFileInfoList, QStringList> FoldersModel::computeFileInfoList(QStringList folders) | ||
65 | 122 | { | ||
66 | 123 | QFileInfoList filteredFileInfoList; | ||
67 | 124 | QStringList filesToWatch; | ||
68 | 125 | |||
69 | 126 | Q_FOREACH (QString folder, folders) { | ||
70 | 88 | QDir currentDir(folder); | 127 | QDir currentDir(folder); |
71 | 89 | QFileInfoList fileInfoList = currentDir.entryInfoList(QDir::Files | QDir::Readable, | 128 | QFileInfoList fileInfoList = currentDir.entryInfoList(QDir::Files | QDir::Readable, |
72 | 90 | QDir::Time | QDir::Reversed); | 129 | QDir::Time | QDir::Reversed); |
73 | 91 | Q_FOREACH (QFileInfo fileInfo, fileInfoList) { | 130 | Q_FOREACH (QFileInfo fileInfo, fileInfoList) { |
76 | 92 | if (fileInfo.isDir()) continue; | 131 | filesToWatch.append(fileInfo.absoluteFilePath()); |
75 | 93 | m_watcher->addPath(fileInfo.absoluteFilePath()); | ||
77 | 94 | if (fileMatchesTypeFilters(fileInfo)) { | 132 | if (fileMatchesTypeFilters(fileInfo)) { |
79 | 95 | insertFileInfo(fileInfo, false); | 133 | filteredFileInfoList.append(fileInfo); |
80 | 96 | } | 134 | } |
81 | 97 | } | 135 | } |
82 | 98 | } | 136 | } |
83 | 137 | qSort(filteredFileInfoList.begin(), filteredFileInfoList.end(), newerThan); | ||
84 | 138 | return QPair<QFileInfoList, QStringList>(filteredFileInfoList, filesToWatch); | ||
85 | 139 | } | ||
86 | 140 | |||
87 | 141 | void FoldersModel::updateFileInfoListFinished() | ||
88 | 142 | { | ||
89 | 143 | QPair<QFileInfoList, QStringList> result = m_updateFutureWatcher.result(); | ||
90 | 144 | setFileInfoList(result.first, result.second); | ||
91 | 145 | } | ||
92 | 146 | |||
93 | 147 | void FoldersModel::setFileInfoList(const QFileInfoList& fileInfoList, const QStringList& filesToWatch) | ||
94 | 148 | { | ||
95 | 149 | beginResetModel(); | ||
96 | 150 | m_fileInfoList = fileInfoList; | ||
97 | 99 | endResetModel(); | 151 | endResetModel(); |
99 | 100 | m_selectedFiles.clear(); | 152 | |
100 | 153 | // Start monitoring files for modifications in a separate thread as it is very time consuming | ||
101 | 154 | QtConcurrent::run(m_watcher, &QFileSystemWatcher::addPaths, filesToWatch); | ||
102 | 155 | |||
103 | 156 | m_loading = false; | ||
104 | 157 | Q_EMIT loadingChanged(); | ||
105 | 101 | Q_EMIT countChanged(); | 158 | Q_EMIT countChanged(); |
106 | 102 | Q_EMIT selectedFilesChanged(); | ||
107 | 103 | } | ||
108 | 104 | |||
109 | 105 | bool moreRecentThan(const QFileInfo& fileInfo1, const QFileInfo& fileInfo2) | ||
110 | 106 | { | ||
111 | 107 | return fileInfo1.lastModified() < fileInfo2.lastModified(); | ||
112 | 108 | } | 159 | } |
113 | 109 | 160 | ||
114 | 110 | bool FoldersModel::fileMatchesTypeFilters(const QFileInfo& newFileInfo) | 161 | bool FoldersModel::fileMatchesTypeFilters(const QFileInfo& newFileInfo) |
115 | @@ -120,33 +171,25 @@ | |||
116 | 120 | 171 | ||
117 | 121 | // inserts newFileInfo into m_fileInfoList while keeping m_fileInfoList sorted by | 172 | // inserts newFileInfo into m_fileInfoList while keeping m_fileInfoList sorted by |
118 | 122 | // file modification time with the files most recently modified first | 173 | // file modification time with the files most recently modified first |
120 | 123 | void FoldersModel::insertFileInfo(const QFileInfo& newFileInfo, bool emitChange) | 174 | void FoldersModel::insertFileInfo(const QFileInfo& newFileInfo) |
121 | 124 | { | 175 | { |
122 | 125 | QFileInfoList::iterator i; | 176 | QFileInfoList::iterator i; |
123 | 126 | for (i = m_fileInfoList.begin(); i != m_fileInfoList.end(); ++i) { | 177 | for (i = m_fileInfoList.begin(); i != m_fileInfoList.end(); ++i) { |
124 | 127 | QFileInfo fileInfo = *i; | 178 | QFileInfo fileInfo = *i; |
134 | 128 | if (!moreRecentThan(newFileInfo, fileInfo)) { | 179 | if (newerThan(newFileInfo, fileInfo)) { |
135 | 129 | if (emitChange) { | 180 | int index = m_fileInfoList.indexOf(*i); |
136 | 130 | int index = m_fileInfoList.indexOf(*i); | 181 | beginInsertRows(QModelIndex(), index, index); |
137 | 131 | beginInsertRows(QModelIndex(), index, index); | 182 | m_fileInfoList.insert(i, newFileInfo); |
138 | 132 | m_fileInfoList.insert(i, newFileInfo); | 183 | endInsertRows(); |
130 | 133 | endInsertRows(); | ||
131 | 134 | } else { | ||
132 | 135 | m_fileInfoList.insert(i, newFileInfo); | ||
133 | 136 | } | ||
139 | 137 | return; | 184 | return; |
140 | 138 | } | 185 | } |
141 | 139 | } | 186 | } |
142 | 140 | 187 | ||
152 | 141 | if (emitChange) { | 188 | int index = m_fileInfoList.size(); |
153 | 142 | int index = m_fileInfoList.size(); | 189 | beginInsertRows(QModelIndex(), index, index); |
154 | 143 | beginInsertRows(QModelIndex(), index, index); | 190 | m_fileInfoList.append(newFileInfo); |
155 | 144 | m_fileInfoList.append(newFileInfo); | 191 | endInsertRows(); |
156 | 145 | endInsertRows(); | 192 | Q_EMIT countChanged(); |
148 | 146 | Q_EMIT countChanged(); | ||
149 | 147 | } else { | ||
150 | 148 | m_fileInfoList.append(newFileInfo); | ||
151 | 149 | } | ||
157 | 150 | } | 193 | } |
158 | 151 | 194 | ||
159 | 152 | QHash<int, QByteArray> FoldersModel::roleNames() const | 195 | QHash<int, QByteArray> FoldersModel::roleNames() const |
160 | @@ -205,31 +248,6 @@ | |||
161 | 205 | return data(index(row), roleNames().key(role.toUtf8())); | 248 | return data(index(row), roleNames().key(role.toUtf8())); |
162 | 206 | } | 249 | } |
163 | 207 | 250 | ||
164 | 208 | void FoldersModel::directoryChanged(const QString &directoryPath) | ||
165 | 209 | { | ||
166 | 210 | /* Only react when a file is added. Ignore when a file was modified or | ||
167 | 211 | * deleted as this is taken care of by FoldersModel::fileChanged() | ||
168 | 212 | * To do so we go through all the files in directoryPath and add | ||
169 | 213 | * all the ones we were not watching before. | ||
170 | 214 | */ | ||
171 | 215 | QStringList watchedFiles = m_watcher->files(); | ||
172 | 216 | QDir directory(directoryPath); | ||
173 | 217 | QStringList files = directory.entryList(QDir::Files | QDir::Readable, | ||
174 | 218 | QDir::Time | QDir::Reversed); | ||
175 | 219 | |||
176 | 220 | Q_FOREACH (QString fileName, files) { | ||
177 | 221 | QString filePath = directory.absoluteFilePath(fileName); | ||
178 | 222 | if (!watchedFiles.contains(filePath)) { | ||
179 | 223 | QFileInfo fileInfo(filePath); | ||
180 | 224 | if (fileInfo.isDir()) continue; | ||
181 | 225 | m_watcher->addPath(filePath); | ||
182 | 226 | if (fileMatchesTypeFilters(fileInfo)) { | ||
183 | 227 | insertFileInfo(fileInfo, true); | ||
184 | 228 | } | ||
185 | 229 | } | ||
186 | 230 | } | ||
187 | 231 | } | ||
188 | 232 | |||
189 | 233 | void FoldersModel::fileChanged(const QString &filePath) | 251 | void FoldersModel::fileChanged(const QString &filePath) |
190 | 234 | { | 252 | { |
191 | 235 | /* Act appropriately upon file change or removal */ | 253 | /* Act appropriately upon file change or removal */ |
192 | @@ -242,7 +260,7 @@ | |||
193 | 242 | if (fileIndex == -1) { | 260 | if (fileIndex == -1) { |
194 | 243 | // file's type might have changed and file might have to be included | 261 | // file's type might have changed and file might have to be included |
195 | 244 | if (fileMatchesTypeFilters(fileInfo)) { | 262 | if (fileMatchesTypeFilters(fileInfo)) { |
197 | 245 | insertFileInfo(fileInfo, true); | 263 | insertFileInfo(fileInfo); |
198 | 246 | } | 264 | } |
199 | 247 | } else { | 265 | } else { |
200 | 248 | // update file information | 266 | // update file information |
201 | @@ -290,10 +308,44 @@ | |||
202 | 290 | Q_EMIT selectedFilesChanged(); | 308 | Q_EMIT selectedFilesChanged(); |
203 | 291 | } | 309 | } |
204 | 292 | 310 | ||
205 | 311 | void FoldersModel::prependFile(QString filePath) | ||
206 | 312 | { | ||
207 | 313 | if (!m_watcher->files().contains(filePath)) { | ||
208 | 314 | QFileInfo fileInfo(filePath); | ||
209 | 315 | m_watcher->addPath(filePath); | ||
210 | 316 | if (fileMatchesTypeFilters(fileInfo)) { | ||
211 | 317 | insertFileInfo(fileInfo); | ||
212 | 318 | } | ||
213 | 319 | } | ||
214 | 320 | } | ||
215 | 321 | |||
216 | 293 | void FoldersModel::selectAll() | 322 | void FoldersModel::selectAll() |
217 | 294 | { | 323 | { |
218 | 295 | for (int row = 0; row < m_fileInfoList.size(); ++row) { | 324 | for (int row = 0; row < m_fileInfoList.size(); ++row) { |
222 | 296 | if (!m_selectedFiles.contains(row)) | 325 | if (!m_selectedFiles.contains(row)) { |
223 | 297 | toggleSelected(row); | 326 | m_selectedFiles.insert(row); |
224 | 298 | } | 327 | } |
225 | 328 | Q_EMIT dataChanged(index(row), index(row)); | ||
226 | 329 | } | ||
227 | 330 | Q_EMIT selectedFilesChanged(); | ||
228 | 331 | } | ||
229 | 332 | |||
230 | 333 | void FoldersModel::deleteSelectedFiles() | ||
231 | 334 | { | ||
232 | 335 | Q_FOREACH (int selectedFile, m_selectedFiles) { | ||
233 | 336 | QString filePath = m_fileInfoList.at(selectedFile).filePath(); | ||
234 | 337 | QFile::remove(filePath); | ||
235 | 338 | } | ||
236 | 339 | m_selectedFiles.clear(); | ||
237 | 340 | Q_EMIT selectedFilesChanged(); | ||
238 | 341 | } | ||
239 | 342 | |||
240 | 343 | void FoldersModel::classBegin() | ||
241 | 344 | { | ||
242 | 345 | } | ||
243 | 346 | |||
244 | 347 | void FoldersModel::componentComplete() | ||
245 | 348 | { | ||
246 | 349 | m_completed = true; | ||
247 | 350 | updateFileInfoList(); | ||
248 | 299 | } | 351 | } |
249 | 300 | 352 | ||
250 | === modified file 'CameraApp/foldersmodel.h' | |||
251 | --- CameraApp/foldersmodel.h 2014-11-26 14:45:34 +0000 | |||
252 | +++ CameraApp/foldersmodel.h 2015-01-24 18:49:40 +0000 | |||
253 | @@ -23,8 +23,10 @@ | |||
254 | 23 | #include <QtCore/QFileSystemWatcher> | 23 | #include <QtCore/QFileSystemWatcher> |
255 | 24 | #include <QtCore/QMimeDatabase> | 24 | #include <QtCore/QMimeDatabase> |
256 | 25 | #include <QtCore/QSet> | 25 | #include <QtCore/QSet> |
257 | 26 | #include <QtCore/QFutureWatcher> | ||
258 | 27 | #include <QtQml/QQmlParserStatus> | ||
259 | 26 | 28 | ||
261 | 27 | class FoldersModel : public QAbstractListModel | 29 | class FoldersModel : public QAbstractListModel, public QQmlParserStatus |
262 | 28 | { | 30 | { |
263 | 29 | Q_OBJECT | 31 | Q_OBJECT |
264 | 30 | Q_PROPERTY (QStringList folders READ folders WRITE setFolders NOTIFY foldersChanged) | 32 | Q_PROPERTY (QStringList folders READ folders WRITE setFolders NOTIFY foldersChanged) |
265 | @@ -32,6 +34,7 @@ | |||
266 | 32 | Q_PROPERTY (QList<int> selectedFiles READ selectedFiles NOTIFY selectedFilesChanged) | 34 | Q_PROPERTY (QList<int> selectedFiles READ selectedFiles NOTIFY selectedFilesChanged) |
267 | 33 | Q_PROPERTY (bool singleSelectionOnly READ singleSelectionOnly WRITE setSingleSelectionOnly NOTIFY singleSelectionOnlyChanged) | 35 | Q_PROPERTY (bool singleSelectionOnly READ singleSelectionOnly WRITE setSingleSelectionOnly NOTIFY singleSelectionOnlyChanged) |
268 | 34 | Q_PROPERTY(int count READ count NOTIFY countChanged) | 36 | Q_PROPERTY(int count READ count NOTIFY countChanged) |
269 | 37 | Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged) | ||
270 | 35 | 38 | ||
271 | 36 | public: | 39 | public: |
272 | 37 | enum Roles { | 40 | enum Roles { |
273 | @@ -52,10 +55,13 @@ | |||
274 | 52 | bool singleSelectionOnly() const; | 55 | bool singleSelectionOnly() const; |
275 | 53 | void setSingleSelectionOnly(bool singleSelectionOnly); | 56 | void setSingleSelectionOnly(bool singleSelectionOnly); |
276 | 54 | int count() const; | 57 | int count() const; |
277 | 58 | bool loading() const; | ||
278 | 55 | 59 | ||
279 | 56 | void updateFileInfoList(); | 60 | void updateFileInfoList(); |
280 | 61 | QPair<QFileInfoList, QStringList> computeFileInfoList(QStringList folders); | ||
281 | 57 | bool fileMatchesTypeFilters(const QFileInfo& newFileInfo); | 62 | bool fileMatchesTypeFilters(const QFileInfo& newFileInfo); |
283 | 58 | void insertFileInfo(const QFileInfo& newFileInfo, bool emitChange); | 63 | void insertFileInfo(const QFileInfo& newFileInfo); |
284 | 64 | void setFileInfoList(const QFileInfoList& fileInfoList, const QStringList& filesToWatch); | ||
285 | 59 | 65 | ||
286 | 60 | QHash<int, QByteArray> roleNames() const; | 66 | QHash<int, QByteArray> roleNames() const; |
287 | 61 | QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; | 67 | QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; |
288 | @@ -64,10 +70,16 @@ | |||
289 | 64 | Q_INVOKABLE void toggleSelected(int row); | 70 | Q_INVOKABLE void toggleSelected(int row); |
290 | 65 | Q_INVOKABLE void clearSelection(); | 71 | Q_INVOKABLE void clearSelection(); |
291 | 66 | Q_INVOKABLE void selectAll(); | 72 | Q_INVOKABLE void selectAll(); |
292 | 73 | Q_INVOKABLE void prependFile(QString filePath); | ||
293 | 74 | Q_INVOKABLE void deleteSelectedFiles(); | ||
294 | 75 | |||
295 | 76 | // inherited from QQmlParserStatus | ||
296 | 77 | void classBegin(); | ||
297 | 78 | void componentComplete(); | ||
298 | 67 | 79 | ||
299 | 68 | public Q_SLOTS: | 80 | public Q_SLOTS: |
300 | 69 | void directoryChanged(const QString &directoryPath); | ||
301 | 70 | void fileChanged(const QString &directoryPath); | 81 | void fileChanged(const QString &directoryPath); |
302 | 82 | void updateFileInfoListFinished(); | ||
303 | 71 | 83 | ||
304 | 72 | Q_SIGNALS: | 84 | Q_SIGNALS: |
305 | 73 | void foldersChanged(); | 85 | void foldersChanged(); |
306 | @@ -75,6 +87,7 @@ | |||
307 | 75 | void selectedFilesChanged(); | 87 | void selectedFilesChanged(); |
308 | 76 | void singleSelectionOnlyChanged(); | 88 | void singleSelectionOnlyChanged(); |
309 | 77 | void countChanged(); | 89 | void countChanged(); |
310 | 90 | void loadingChanged(); | ||
311 | 78 | 91 | ||
312 | 79 | private: | 92 | private: |
313 | 80 | QStringList m_folders; | 93 | QStringList m_folders; |
314 | @@ -84,6 +97,9 @@ | |||
315 | 84 | QMimeDatabase m_mimeDatabase; | 97 | QMimeDatabase m_mimeDatabase; |
316 | 85 | QSet<int> m_selectedFiles; | 98 | QSet<int> m_selectedFiles; |
317 | 86 | bool m_singleSelectionOnly; | 99 | bool m_singleSelectionOnly; |
318 | 100 | QFutureWatcher<QPair<QFileInfoList, QStringList> > m_updateFutureWatcher; | ||
319 | 101 | bool m_completed; | ||
320 | 102 | bool m_loading; | ||
321 | 87 | }; | 103 | }; |
322 | 88 | 104 | ||
323 | 89 | #endif // FOLDERSMODEL_H | 105 | #endif // FOLDERSMODEL_H |
324 | 90 | 106 | ||
325 | === modified file 'GalleryView.qml' | |||
326 | --- GalleryView.qml 2014-12-05 18:50:53 +0000 | |||
327 | +++ GalleryView.qml 2015-01-24 18:49:40 +0000 | |||
328 | @@ -47,6 +47,10 @@ | |||
329 | 47 | showLastPhotoTakenPending = true; | 47 | showLastPhotoTakenPending = true; |
330 | 48 | } | 48 | } |
331 | 49 | 49 | ||
332 | 50 | function prependMediaToModel(filePath) { | ||
333 | 51 | galleryView.model.prependFile(filePath); | ||
334 | 52 | } | ||
335 | 53 | |||
336 | 50 | function exitUserSelectionMode() { | 54 | function exitUserSelectionMode() { |
337 | 51 | if (gridMode) { | 55 | if (gridMode) { |
338 | 52 | model.clearSelection(); | 56 | model.clearSelection(); |
339 | @@ -155,7 +159,7 @@ | |||
340 | 155 | Rectangle { | 159 | Rectangle { |
341 | 156 | objectName: "noMediaHint" | 160 | objectName: "noMediaHint" |
342 | 157 | anchors.fill: parent | 161 | anchors.fill: parent |
344 | 158 | visible: model.count === 0 | 162 | visible: model.count === 0 && !model.loading |
345 | 159 | color: "#0F0F0F" | 163 | color: "#0F0F0F" |
346 | 160 | 164 | ||
347 | 161 | Icon { | 165 | Icon { |
348 | @@ -186,6 +190,40 @@ | |||
349 | 186 | } | 190 | } |
350 | 187 | } | 191 | } |
351 | 188 | 192 | ||
352 | 193 | Rectangle { | ||
353 | 194 | objectName: "scanningMediaHint" | ||
354 | 195 | anchors.fill: parent | ||
355 | 196 | visible: model.loading | ||
356 | 197 | color: "#0F0F0F" | ||
357 | 198 | |||
358 | 199 | Icon { | ||
359 | 200 | id: scanningMediaIcon | ||
360 | 201 | anchors { | ||
361 | 202 | horizontalCenter: parent.horizontalCenter | ||
362 | 203 | verticalCenter: parent.verticalCenter | ||
363 | 204 | verticalCenterOffset: -units.gu(1) | ||
364 | 205 | } | ||
365 | 206 | height: units.gu(9) | ||
366 | 207 | width: units.gu(9) | ||
367 | 208 | color: "white" | ||
368 | 209 | opacity: 0.2 | ||
369 | 210 | name: "camera-app-symbolic" | ||
370 | 211 | } | ||
371 | 212 | |||
372 | 213 | Label { | ||
373 | 214 | id: scanningMediaLabel | ||
374 | 215 | anchors { | ||
375 | 216 | horizontalCenter: parent.horizontalCenter | ||
376 | 217 | top: scanningMediaIcon.bottom | ||
377 | 218 | topMargin: units.gu(4) | ||
378 | 219 | } | ||
379 | 220 | text: i18n.tr("Scanning for content...") | ||
380 | 221 | color: "white" | ||
381 | 222 | opacity: 0.2 | ||
382 | 223 | fontSize: "large" | ||
383 | 224 | } | ||
384 | 225 | } | ||
385 | 226 | |||
386 | 189 | state: galleryView.gridMode || main.contentExportMode ? "GRID" : "SLIDESHOW" | 227 | state: galleryView.gridMode || main.contentExportMode ? "GRID" : "SLIDESHOW" |
387 | 190 | states: [ | 228 | states: [ |
388 | 191 | State { | 229 | State { |
389 | 192 | 230 | ||
390 | === modified file 'GalleryViewLoader.qml' | |||
391 | --- GalleryViewLoader.qml 2014-08-27 00:31:30 +0000 | |||
392 | +++ GalleryViewLoader.qml 2015-01-24 18:49:40 +0000 | |||
393 | @@ -27,6 +27,11 @@ | |||
394 | 27 | loader.item.showLastPhotoTaken(); | 27 | loader.item.showLastPhotoTaken(); |
395 | 28 | } | 28 | } |
396 | 29 | 29 | ||
397 | 30 | function prependMediaToModel(filePath) { | ||
398 | 31 | loader.item.prependMediaToModel(filePath); | ||
399 | 32 | } | ||
400 | 33 | |||
401 | 34 | |||
402 | 30 | asynchronous: true | 35 | asynchronous: true |
403 | 31 | 36 | ||
404 | 32 | Component.onCompleted: { | 37 | Component.onCompleted: { |
405 | 33 | 38 | ||
406 | === modified file 'PhotogridView.qml' | |||
407 | --- PhotogridView.qml 2014-12-03 12:37:15 +0000 | |||
408 | +++ PhotogridView.qml 2015-01-24 18:49:40 +0000 | |||
409 | @@ -210,12 +210,7 @@ | |||
410 | 210 | } | 210 | } |
411 | 211 | 211 | ||
412 | 212 | onDeleteFiles: { | 212 | onDeleteFiles: { |
419 | 213 | for (var i=model.selectedFiles.length-1; i>=0; i--) { | 213 | model.deleteSelectedFiles(); |
414 | 214 | var currentFilePath = model.get(model.selectedFiles[i], "filePath"); | ||
415 | 215 | model.toggleSelected(model.selectedFiles[i]) | ||
416 | 216 | fileOperations.remove(currentFilePath); | ||
417 | 217 | } | ||
418 | 218 | |||
420 | 219 | photogridView.exitUserSelectionMode(); | 214 | photogridView.exitUserSelectionMode(); |
421 | 220 | } | 215 | } |
422 | 221 | } | 216 | } |
423 | 222 | 217 | ||
424 | === modified file 'SlideshowView.qml' | |||
425 | --- SlideshowView.qml 2014-12-05 18:50:53 +0000 | |||
426 | +++ SlideshowView.qml 2015-01-24 18:49:40 +0000 | |||
427 | @@ -79,6 +79,12 @@ | |||
428 | 79 | // were hitting https://bugreports.qt-project.org/browse/QTBUG-41035 | 79 | // were hitting https://bugreports.qt-project.org/browse/QTBUG-41035 |
429 | 80 | highlightMoveDuration: 0 | 80 | highlightMoveDuration: 0 |
430 | 81 | snapMode: ListView.SnapOneItem | 81 | snapMode: ListView.SnapOneItem |
431 | 82 | onCountChanged: { | ||
432 | 83 | // currentIndex is -1 by default and stays so until manually set to something else | ||
433 | 84 | if (currentIndex == -1 && count != 0) { | ||
434 | 85 | currentIndex = 0; | ||
435 | 86 | } | ||
436 | 87 | } | ||
437 | 82 | spacing: units.gu(1) | 88 | spacing: units.gu(1) |
438 | 83 | interactive: currentItem ? !currentItem.pinchInProgress : true | 89 | interactive: currentItem ? !currentItem.pinchInProgress : true |
439 | 84 | property real maxDimension: Math.max(width, height) | 90 | property real maxDimension: Math.max(width, height) |
440 | 85 | 91 | ||
441 | === modified file 'ViewFinderView.qml' | |||
442 | --- ViewFinderView.qml 2014-12-11 12:24:25 +0000 | |||
443 | +++ ViewFinderView.qml 2015-01-24 18:49:40 +0000 | |||
444 | @@ -29,8 +29,8 @@ | |||
445 | 29 | property bool touchAcquired: viewFinderOverlay.touchAcquired || camera.videoRecorder.recorderState == CameraRecorder.RecordingState | 29 | property bool touchAcquired: viewFinderOverlay.touchAcquired || camera.videoRecorder.recorderState == CameraRecorder.RecordingState |
446 | 30 | property bool inView | 30 | property bool inView |
447 | 31 | property alias captureMode: camera.captureMode | 31 | property alias captureMode: camera.captureMode |
450 | 32 | signal photoTaken | 32 | signal photoTaken(string filePath) |
451 | 33 | signal videoShot | 33 | signal videoShot(string filePath) |
452 | 34 | 34 | ||
453 | 35 | Camera { | 35 | Camera { |
454 | 36 | id: camera | 36 | id: camera |
455 | @@ -84,18 +84,19 @@ | |||
456 | 84 | } | 84 | } |
457 | 85 | onImageCaptured: { | 85 | onImageCaptured: { |
458 | 86 | snapshot.source = preview; | 86 | snapshot.source = preview; |
459 | 87 | if (!main.contentExportMode) { | ||
460 | 88 | viewFinderOverlay.visible = true; | ||
461 | 89 | snapshot.startOutAnimation(); | ||
462 | 90 | if (photoRollHint.necessary) { | ||
463 | 91 | photoRollHint.enable(); | ||
464 | 92 | } | ||
465 | 93 | } | ||
466 | 87 | } | 94 | } |
467 | 88 | onImageSaved: { | 95 | onImageSaved: { |
468 | 89 | if (main.contentExportMode) { | 96 | if (main.contentExportMode) { |
469 | 90 | viewFinderExportConfirmation.confirmExport(path); | 97 | viewFinderExportConfirmation.confirmExport(path); |
470 | 91 | } else { | ||
471 | 92 | viewFinderOverlay.visible = true; | ||
472 | 93 | snapshot.startOutAnimation(); | ||
473 | 94 | if (photoRollHint.necessary) { | ||
474 | 95 | photoRollHint.enable(); | ||
475 | 96 | } | ||
476 | 97 | } | 98 | } |
478 | 98 | viewFinderView.photoTaken(); | 99 | viewFinderView.photoTaken(path); |
479 | 99 | metricPhotos.increment(); | 100 | metricPhotos.increment(); |
480 | 100 | console.log("Picture saved as " + path); | 101 | console.log("Picture saved as " + path); |
481 | 101 | } | 102 | } |
482 | @@ -109,7 +110,7 @@ | |||
483 | 109 | } | 110 | } |
484 | 110 | metricVideos.increment() | 111 | metricVideos.increment() |
485 | 111 | viewFinderOverlay.visible = true; | 112 | viewFinderOverlay.visible = true; |
487 | 112 | viewFinderView.videoShot(); | 113 | viewFinderView.videoShot(videoRecorder.actualLocation); |
488 | 113 | } | 114 | } |
489 | 114 | } | 115 | } |
490 | 115 | } | 116 | } |
491 | 116 | 117 | ||
492 | === modified file 'camera-app.qml' | |||
493 | --- camera-app.qml 2014-09-05 11:43:11 +0000 | |||
494 | +++ camera-app.qml 2015-01-24 18:49:40 +0000 | |||
495 | @@ -152,8 +152,14 @@ | |||
496 | 152 | height: viewSwitcher.height | 152 | height: viewSwitcher.height |
497 | 153 | overlayVisible: !viewSwitcher.moving && !viewSwitcher.flicking | 153 | overlayVisible: !viewSwitcher.moving && !viewSwitcher.flicking |
498 | 154 | inView: !viewSwitcher.atXEnd | 154 | inView: !viewSwitcher.atXEnd |
501 | 155 | onPhotoTaken: galleryView.showLastPhotoTaken(); | 155 | onPhotoTaken: { |
502 | 156 | onVideoShot: galleryView.showLastPhotoTaken(); | 156 | galleryView.prependMediaToModel(filePath); |
503 | 157 | galleryView.showLastPhotoTaken(); | ||
504 | 158 | } | ||
505 | 159 | onVideoShot: { | ||
506 | 160 | galleryView.prependMediaToModel(filePath); | ||
507 | 161 | galleryView.showLastPhotoTaken(); | ||
508 | 162 | } | ||
509 | 157 | } | 163 | } |
510 | 158 | 164 | ||
511 | 159 | GalleryViewLoader { | 165 | GalleryViewLoader { |
512 | 160 | 166 | ||
513 | === modified file 'po/camera-app.pot' | |||
514 | --- po/camera-app.pot 2014-12-15 10:51:14 +0000 | |||
515 | +++ po/camera-app.pot 2015-01-24 18:49:40 +0000 | |||
516 | @@ -8,7 +8,7 @@ | |||
517 | 8 | msgstr "" | 8 | msgstr "" |
518 | 9 | "Project-Id-Version: camera-app\n" | 9 | "Project-Id-Version: camera-app\n" |
519 | 10 | "Report-Msgid-Bugs-To: \n" | 10 | "Report-Msgid-Bugs-To: \n" |
521 | 11 | "POT-Creation-Date: 2014-12-15 08:50-0200\n" | 11 | "POT-Creation-Date: 2015-01-24 15:51-0200\n" |
522 | 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" |
523 | 13 | "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | 13 | "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" |
524 | 14 | "Language-Team: LANGUAGE <LL@li.org>\n" | 14 | "Language-Team: LANGUAGE <LL@li.org>\n" |
525 | @@ -29,10 +29,14 @@ | |||
526 | 29 | msgid "Cancel" | 29 | msgid "Cancel" |
527 | 30 | msgstr "" | 30 | msgstr "" |
528 | 31 | 31 | ||
530 | 32 | #: ../GalleryView.qml:182 | 32 | #: ../GalleryView.qml:186 |
531 | 33 | msgid "No media available." | 33 | msgid "No media available." |
532 | 34 | msgstr "" | 34 | msgstr "" |
533 | 35 | 35 | ||
534 | 36 | #: ../GalleryView.qml:220 | ||
535 | 37 | msgid "Scanning for content..." | ||
536 | 38 | msgstr "" | ||
537 | 39 | |||
538 | 36 | #: ../GalleryViewHeader.qml:75 | 40 | #: ../GalleryViewHeader.qml:75 |
539 | 37 | msgid "Select" | 41 | msgid "Select" |
540 | 38 | msgstr "" | 42 | msgstr "" |
541 | @@ -49,55 +53,55 @@ | |||
542 | 49 | msgid "Share" | 53 | msgid "Share" |
543 | 50 | msgstr "" | 54 | msgstr "" |
544 | 51 | 55 | ||
548 | 52 | #: ../ViewFinderOverlay.qml:185 ../ViewFinderOverlay.qml:208 | 56 | #: ../ViewFinderOverlay.qml:195 ../ViewFinderOverlay.qml:218 |
549 | 53 | #: ../ViewFinderOverlay.qml:236 ../ViewFinderOverlay.qml:259 | 57 | #: ../ViewFinderOverlay.qml:246 ../ViewFinderOverlay.qml:269 |
550 | 54 | #: ../ViewFinderOverlay.qml:336 | 58 | #: ../ViewFinderOverlay.qml:346 |
551 | 55 | msgid "On" | 59 | msgid "On" |
552 | 56 | msgstr "" | 60 | msgstr "" |
553 | 57 | 61 | ||
557 | 58 | #: ../ViewFinderOverlay.qml:190 ../ViewFinderOverlay.qml:218 | 62 | #: ../ViewFinderOverlay.qml:200 ../ViewFinderOverlay.qml:228 |
558 | 59 | #: ../ViewFinderOverlay.qml:241 ../ViewFinderOverlay.qml:264 | 63 | #: ../ViewFinderOverlay.qml:251 ../ViewFinderOverlay.qml:274 |
559 | 60 | #: ../ViewFinderOverlay.qml:283 ../ViewFinderOverlay.qml:341 | 64 | #: ../ViewFinderOverlay.qml:293 ../ViewFinderOverlay.qml:351 |
560 | 61 | msgid "Off" | 65 | msgid "Off" |
561 | 62 | msgstr "" | 66 | msgstr "" |
562 | 63 | 67 | ||
564 | 64 | #: ../ViewFinderOverlay.qml:213 | 68 | #: ../ViewFinderOverlay.qml:223 |
565 | 65 | msgid "Auto" | 69 | msgid "Auto" |
566 | 66 | msgstr "" | 70 | msgstr "" |
567 | 67 | 71 | ||
569 | 68 | #: ../ViewFinderOverlay.qml:250 | 72 | #: ../ViewFinderOverlay.qml:260 |
570 | 69 | msgid "HDR" | 73 | msgid "HDR" |
571 | 70 | msgstr "" | 74 | msgstr "" |
572 | 71 | 75 | ||
574 | 72 | #: ../ViewFinderOverlay.qml:288 | 76 | #: ../ViewFinderOverlay.qml:298 |
575 | 73 | msgid "5 seconds" | 77 | msgid "5 seconds" |
576 | 74 | msgstr "" | 78 | msgstr "" |
577 | 75 | 79 | ||
579 | 76 | #: ../ViewFinderOverlay.qml:293 | 80 | #: ../ViewFinderOverlay.qml:303 |
580 | 77 | msgid "15 seconds" | 81 | msgid "15 seconds" |
581 | 78 | msgstr "" | 82 | msgstr "" |
582 | 79 | 83 | ||
584 | 80 | #: ../ViewFinderOverlay.qml:310 | 84 | #: ../ViewFinderOverlay.qml:320 |
585 | 81 | msgid "Fine Quality" | 85 | msgid "Fine Quality" |
586 | 82 | msgstr "" | 86 | msgstr "" |
587 | 83 | 87 | ||
589 | 84 | #: ../ViewFinderOverlay.qml:314 | 88 | #: ../ViewFinderOverlay.qml:324 |
590 | 85 | msgid "Normal Quality" | 89 | msgid "Normal Quality" |
591 | 86 | msgstr "" | 90 | msgstr "" |
592 | 87 | 91 | ||
594 | 88 | #: ../ViewFinderOverlay.qml:318 | 92 | #: ../ViewFinderOverlay.qml:328 |
595 | 89 | msgid "Basic Quality" | 93 | msgid "Basic Quality" |
596 | 90 | msgstr "" | 94 | msgstr "" |
597 | 91 | 95 | ||
599 | 92 | #: ../ViewFinderOverlay.qml:350 | 96 | #: ../ViewFinderOverlay.qml:360 |
600 | 93 | msgid "SD" | 97 | msgid "SD" |
601 | 94 | msgstr "" | 98 | msgstr "" |
602 | 95 | 99 | ||
604 | 96 | #: ../ViewFinderOverlay.qml:358 | 100 | #: ../ViewFinderOverlay.qml:368 |
605 | 97 | msgid "Save to SD Card" | 101 | msgid "Save to SD Card" |
606 | 98 | msgstr "" | 102 | msgstr "" |
607 | 99 | 103 | ||
609 | 100 | #: ../ViewFinderOverlay.qml:363 | 104 | #: ../ViewFinderOverlay.qml:373 |
610 | 101 | msgid "Save internally" | 105 | msgid "Save internally" |
611 | 102 | msgstr "" | 106 | msgstr "" |
612 | 103 | 107 | ||
613 | @@ -141,21 +145,21 @@ | |||
614 | 141 | msgid "Lighting Condition;Day;Cloudy;Inside" | 145 | msgid "Lighting Condition;Day;Cloudy;Inside" |
615 | 142 | msgstr "" | 146 | msgstr "" |
616 | 143 | 147 | ||
618 | 144 | #: ../camera-app.qml:214 | 148 | #: ../camera-app.qml:220 |
619 | 145 | #, qt-format | 149 | #, qt-format |
620 | 146 | msgid "<b>%1</b> photos taken today" | 150 | msgid "<b>%1</b> photos taken today" |
621 | 147 | msgstr "" | 151 | msgstr "" |
622 | 148 | 152 | ||
624 | 149 | #: ../camera-app.qml:215 | 153 | #: ../camera-app.qml:221 |
625 | 150 | msgid "No photos taken today" | 154 | msgid "No photos taken today" |
626 | 151 | msgstr "" | 155 | msgstr "" |
627 | 152 | 156 | ||
629 | 153 | #: ../camera-app.qml:223 | 157 | #: ../camera-app.qml:229 |
630 | 154 | #, qt-format | 158 | #, qt-format |
631 | 155 | msgid "<b>%1</b> videos recorded today" | 159 | msgid "<b>%1</b> videos recorded today" |
632 | 156 | msgstr "" | 160 | msgstr "" |
633 | 157 | 161 | ||
635 | 158 | #: ../camera-app.qml:224 | 162 | #: ../camera-app.qml:230 |
636 | 159 | msgid "No videos recorded today" | 163 | msgid "No videos recorded today" |
637 | 160 | msgstr "" | 164 | msgstr "" |
638 | 161 | 165 | ||
639 | 162 | 166 | ||
640 | === modified file 'tests/autopilot/camera_app/tests/test_gallery_view.py' | |||
641 | --- tests/autopilot/camera_app/tests/test_gallery_view.py 2014-11-20 22:02:12 +0000 | |||
642 | +++ tests/autopilot/camera_app/tests/test_gallery_view.py 2015-01-24 18:49:40 +0000 | |||
643 | @@ -62,10 +62,11 @@ | |||
644 | 62 | os.remove(os.path.join(self.videos_dir, f)) | 62 | os.remove(os.path.join(self.videos_dir, f)) |
645 | 63 | 63 | ||
646 | 64 | def add_sample_photo(self): | 64 | def add_sample_photo(self): |
651 | 65 | # add a fake photo to pictures_dir | 65 | self.main_window.swipe_to_viewfinder(self) |
652 | 66 | photo_path = os.path.join(self.pictures_dir, "fake_photo.jpg") | 66 | exposure_button = self.main_window.get_exposure_button() |
653 | 67 | with open(photo_path, 'a'): | 67 | self.assertThat(exposure_button.enabled, Eventually(Equals(True))) |
654 | 68 | os.utime(photo_path, None) | 68 | self.pointing_device.move_to_object(exposure_button) |
655 | 69 | self.pointing_device.click() | ||
656 | 69 | 70 | ||
657 | 70 | def select_first_photo(self): | 71 | def select_first_photo(self): |
658 | 71 | # select the first photo | 72 | # select the first photo |
FAILED: Continuous integration, rev:474 jenkins. qa.ubuntu. com/job/ camera- app-ci/ 358/ jenkins. qa.ubuntu. com/job/ camera- app-vivid- amd64-ci/ 54 jenkins. qa.ubuntu. com/job/ camera- app-vivid- armhf-ci/ 54 jenkins. qa.ubuntu. com/job/ camera- app-vivid- armhf-ci/ 54/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ camera- app-vivid- i386-ci/ 54 jenkins. qa.ubuntu. com/job/ generic- click-autopilot -vivid- touch/88/ console jenkins. qa.ubuntu. com/job/ generic- mediumtests- vivid/439 jenkins. qa.ubuntu. com/job/ generic- click-autopilot -runner- mako/728/ console jenkins. qa.ubuntu. com/job/ generic- click-builder- vivid-armhf/ 183 s-jenkins. ubuntu- ci:8080/ job/touch- flash-device/ 17406 jenkins. qa.ubuntu. com/job/ autopilot- testrunner- otto-vivid/ 360 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- vivid-amd64/ 531 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- vivid-amd64/ 531/artifact/ work/output/ *zip*/output. zip
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
UNSTABLE: http://
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/camera- app-ci/ 358/rebuild
http://