Merge lp:~jamesh/mediascanner2/bug-1489656 into lp:mediascanner2
- bug-1489656
- Merge into trunk
Proposed by
James Henstridge
Status: | Merged |
---|---|
Approved by: | Michi Henning |
Approved revision: | 339 |
Merged at revision: | 331 |
Proposed branch: | lp:~jamesh/mediascanner2/bug-1489656 |
Merge into: | lp:mediascanner2 |
Diff against target: |
835 lines (+552/-112) 11 files modified
CMakeLists.txt (+3/-2) debian/changelog (+8/-0) src/daemon/CMakeLists.txt (+1/-0) src/daemon/InvalidationSender.cc (+4/-0) src/daemon/InvalidationSender.hh (+4/-0) src/daemon/SubtreeWatcher.hh (+2/-3) src/daemon/VolumeManager.cc (+232/-0) src/daemon/VolumeManager.hh (+49/-0) src/daemon/scannerdaemon.cc (+8/-107) test/CMakeLists.txt (+7/-0) test/test_volumemanager.cc (+234/-0) |
To merge this branch: | bzr merge lp:~jamesh/mediascanner2/bug-1489656 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michi Henning (community) | Approve | ||
unity-api-1-bot | continuous-integration | Needs Fixing | |
Review via email: mp+304350@code.launchpad.net |
Commit message
When multiple volumes are mounted in quick succession, scan them serially to avoid reentrancy problems in the initial scan.
Description of the change
Queue up volume mount/unmount events so they are processed serially rather than all at once.
To post a comment you must log in.
Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote : | # |
review:
Needs Fixing
(continuous-integration)
Revision history for this message
Michi Henning (michihenning) wrote : | # |
From reading the code, this looks nice to me. I'm not familiar with the code though. Approving seeing that there is no other suitable candidate.
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'CMakeLists.txt' |
2 | --- CMakeLists.txt 2016-02-25 01:53:30 +0000 |
3 | +++ CMakeLists.txt 2016-08-31 03:52:20 +0000 |
4 | @@ -1,7 +1,7 @@ |
5 | +cmake_minimum_required(VERSION 3.0.2) |
6 | project(mediascanner2 CXX C) |
7 | -cmake_minimum_required(VERSION 2.8.9) |
8 | |
9 | -set(MEDIASCANNER_VERSION "0.111") |
10 | +set(MEDIASCANNER_VERSION "0.112") |
11 | |
12 | execute_process( |
13 | COMMAND /bin/sh ${CMAKE_CURRENT_SOURCE_DIR}/get-soversion.sh |
14 | @@ -93,6 +93,7 @@ |
15 | test_sqliteutils |
16 | test_mfbuilder |
17 | test_subtreewatcher |
18 | + test_volumemanager |
19 | test_dbus |
20 | test_qml |
21 | test_util |
22 | |
23 | === modified file 'debian/changelog' |
24 | --- debian/changelog 2016-05-26 06:02:56 +0000 |
25 | +++ debian/changelog 2016-08-31 03:52:20 +0000 |
26 | @@ -1,3 +1,11 @@ |
27 | +mediascanner2 (0.112-0ubuntu1) UNRELEASED; urgency=medium |
28 | + |
29 | + * When multiple volumes are mounted in quick succession, scan them |
30 | + serially to avoid reentrancy problems in the initial scan. (LP: |
31 | + #1489656) |
32 | + |
33 | + -- James Henstridge <james.henstridge@canonical.com> Wed, 31 Aug 2016 11:26:18 +0800 |
34 | + |
35 | mediascanner2 (0.111+16.10.20160526-0ubuntu1) yakkety; urgency=medium |
36 | |
37 | [ Alex Tu ] |
38 | |
39 | === modified file 'src/daemon/CMakeLists.txt' |
40 | --- src/daemon/CMakeLists.txt 2015-11-02 09:38:48 +0000 |
41 | +++ src/daemon/CMakeLists.txt 2016-08-31 03:52:20 +0000 |
42 | @@ -4,6 +4,7 @@ |
43 | add_library(scannerstuff STATIC |
44 | InvalidationSender.cc |
45 | MountWatcher.cc |
46 | + VolumeManager.cc |
47 | SubtreeWatcher.cc |
48 | Scanner.cc |
49 | ../mediascanner/utils.cc |
50 | |
51 | === modified file 'src/daemon/InvalidationSender.cc' |
52 | --- src/daemon/InvalidationSender.cc 2016-03-16 07:13:27 +0000 |
53 | +++ src/daemon/InvalidationSender.cc 2016-08-31 03:52:20 +0000 |
54 | @@ -31,6 +31,8 @@ |
55 | static const char SCOPES_DBUS_PATH[] = "/com/canonical/unity/scopes"; |
56 | static const char SCOPES_INVALIDATE_RESULTS[] = "InvalidateResults"; |
57 | |
58 | +namespace mediascanner { |
59 | + |
60 | InvalidationSender::InvalidationSender() : |
61 | bus(nullptr, g_object_unref) { |
62 | } |
63 | @@ -87,3 +89,5 @@ |
64 | invalidator->timeout_id = 0; |
65 | return G_SOURCE_REMOVE; |
66 | } |
67 | + |
68 | +} |
69 | |
70 | === modified file 'src/daemon/InvalidationSender.hh' |
71 | --- src/daemon/InvalidationSender.hh 2016-03-16 07:13:27 +0000 |
72 | +++ src/daemon/InvalidationSender.hh 2016-08-31 03:52:20 +0000 |
73 | @@ -24,6 +24,8 @@ |
74 | |
75 | typedef struct _GDBusConnection GDBusConnection; |
76 | |
77 | +namespace mediascanner { |
78 | + |
79 | /** |
80 | * A class that sends a broadcast signal that the state of media |
81 | * files has changed. |
82 | @@ -48,4 +50,6 @@ |
83 | int delay = 0; |
84 | }; |
85 | |
86 | +} |
87 | + |
88 | #endif |
89 | |
90 | === modified file 'src/daemon/SubtreeWatcher.hh' |
91 | --- src/daemon/SubtreeWatcher.hh 2016-02-09 03:36:14 +0000 |
92 | +++ src/daemon/SubtreeWatcher.hh 2016-08-31 03:52:20 +0000 |
93 | @@ -22,10 +22,9 @@ |
94 | |
95 | #include<string> |
96 | |
97 | +namespace mediascanner { |
98 | + |
99 | class InvalidationSender; |
100 | - |
101 | -namespace mediascanner { |
102 | - |
103 | class MediaStore; |
104 | class MetadataExtractor; |
105 | |
106 | |
107 | === added file 'src/daemon/VolumeManager.cc' |
108 | --- src/daemon/VolumeManager.cc 1970-01-01 00:00:00 +0000 |
109 | +++ src/daemon/VolumeManager.cc 2016-08-31 03:52:20 +0000 |
110 | @@ -0,0 +1,232 @@ |
111 | +/* |
112 | + * Copyright (C) 2016 Canonical, Ltd. |
113 | + * |
114 | + * Authors: |
115 | + * James Henstridge <james.henstridge@canonical.com> |
116 | + * |
117 | + * This library is free software; you can redistribute it and/or modify it under |
118 | + * the terms of version 3 of the GNU General Public License as published |
119 | + * by the Free Software Foundation. |
120 | + * |
121 | + * This library is distributed in the hope that it will be useful, but WITHOUT |
122 | + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
123 | + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
124 | + * details. |
125 | + * |
126 | + * You should have received a copy of the GNU General Public License |
127 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
128 | + */ |
129 | + |
130 | +#include "VolumeManager.hh" |
131 | + |
132 | +#include <mediascanner/MediaFile.hh> |
133 | +#include <mediascanner/MediaStore.hh> |
134 | +#include <extractor/DetectedFile.hh> |
135 | +#include <extractor/MetadataExtractor.hh> |
136 | +#include "InvalidationSender.hh" |
137 | +#include "Scanner.hh" |
138 | +#include "SubtreeWatcher.hh" |
139 | +#include "../mediascanner/internal/utils.hh" |
140 | + |
141 | +#include <glib.h> |
142 | + |
143 | +#include <cassert> |
144 | +#include <cstdio> |
145 | +#include <map> |
146 | +#include <deque> |
147 | + |
148 | +using namespace std; |
149 | + |
150 | +namespace { |
151 | + |
152 | +enum class VolumeEventType { |
153 | + added, |
154 | + removed, |
155 | +}; |
156 | + |
157 | +struct VolumeEvent { |
158 | + VolumeEventType type; |
159 | + string path; |
160 | + |
161 | + VolumeEvent(VolumeEventType type, const string& path) |
162 | + : type(type), path(path) {} |
163 | +}; |
164 | + |
165 | +} |
166 | + |
167 | +namespace mediascanner { |
168 | + |
169 | +struct VolumeManagerPrivate { |
170 | + MediaStore& store; |
171 | + MetadataExtractor& extractor; |
172 | + InvalidationSender& invalidator; |
173 | + |
174 | + map<string, unique_ptr<SubtreeWatcher>> volumes; |
175 | + deque<VolumeEvent> pending; |
176 | + unsigned int idle_id = 0; |
177 | + |
178 | + VolumeManagerPrivate(MediaStore& store, MetadataExtractor& extractor, |
179 | + InvalidationSender& invalidator); |
180 | + ~VolumeManagerPrivate(); |
181 | + |
182 | + void queueUpdate(VolumeEventType type, const string& path); |
183 | + static gboolean processEvent(void *user_data) noexcept; |
184 | + |
185 | + void addVolume(const string& path); |
186 | + void removeVolume(const string& path); |
187 | + void readFiles(const string& subdir, const MediaType type); |
188 | +}; |
189 | + |
190 | +VolumeManager::VolumeManager(MediaStore& store, MetadataExtractor& extractor, |
191 | + InvalidationSender& invalidator) |
192 | + : p(new VolumeManagerPrivate(store, extractor, invalidator)) { |
193 | +} |
194 | + |
195 | +VolumeManager::~VolumeManager() = default; |
196 | + |
197 | +void VolumeManager::queueAddVolume(const string& path) { |
198 | + p->queueUpdate(VolumeEventType::added, path); |
199 | +} |
200 | + |
201 | +void VolumeManager::queueRemoveVolume(const string& path) { |
202 | + p->queueUpdate(VolumeEventType::removed, path); |
203 | +} |
204 | + |
205 | +bool VolumeManager::idle() const { |
206 | + // idle_id will only be reset once the scanning job has completed. |
207 | + return p->idle_id == 0; |
208 | +} |
209 | + |
210 | +VolumeManagerPrivate::VolumeManagerPrivate(MediaStore& store, |
211 | + MetadataExtractor& extractor, |
212 | + InvalidationSender& invalidator) |
213 | + : store(store), extractor(extractor), invalidator(invalidator) { |
214 | +} |
215 | + |
216 | +VolumeManagerPrivate::~VolumeManagerPrivate() |
217 | +{ |
218 | + if (idle_id != 0) { |
219 | + g_source_remove(idle_id); |
220 | + } |
221 | +} |
222 | + |
223 | +void VolumeManagerPrivate::queueUpdate(VolumeEventType type, |
224 | + const string& path) { |
225 | + for (auto it = pending.begin(); it != pending.end();) { |
226 | + if (it->path == path) { |
227 | + it = pending.erase(it); |
228 | + } else { |
229 | + ++it; |
230 | + } |
231 | + } |
232 | + pending.emplace_back(type, path); |
233 | + if (idle_id == 0) { |
234 | + idle_id = g_idle_add(&VolumeManagerPrivate::processEvent, this); |
235 | + } |
236 | +} |
237 | + |
238 | +gboolean VolumeManagerPrivate::processEvent(void *user_data) noexcept { |
239 | + auto *p = reinterpret_cast<VolumeManagerPrivate*>(user_data); |
240 | + |
241 | + while (!p->pending.empty()) { |
242 | + auto event = move(p->pending.front()); |
243 | + p->pending.pop_front(); |
244 | + |
245 | + switch (event.type) { |
246 | + case VolumeEventType::added: |
247 | + p->addVolume(event.path); |
248 | + break; |
249 | + case VolumeEventType::removed: |
250 | + p->removeVolume(event.path); |
251 | + break; |
252 | + } |
253 | + } |
254 | + p->invalidator.invalidate(); |
255 | + p->idle_id = 0; |
256 | + return G_SOURCE_REMOVE; |
257 | +} |
258 | + |
259 | +void VolumeManagerPrivate::addVolume(const string& path) { |
260 | + assert(path[0] == '/'); |
261 | + if(volumes.find(path) != volumes.end()) { |
262 | + return; |
263 | + } |
264 | + if(is_rootlike(path)) { |
265 | + fprintf(stderr, "Directory %s looks like a top level root directory, skipping it (%s).\n", |
266 | + path.c_str(), __PRETTY_FUNCTION__); |
267 | + return; |
268 | + } |
269 | + if(is_optical_disc(path)) { |
270 | + fprintf(stderr, "Directory %s looks like an optical disc, skipping it.\n", path.c_str()); |
271 | + return; |
272 | + } |
273 | + if(has_scanblock(path)) { |
274 | + fprintf(stderr, "Directory %s has a scan block file, skipping it.\n", path.c_str()); |
275 | + return; |
276 | + } |
277 | + unique_ptr<SubtreeWatcher> sw(new SubtreeWatcher(store, extractor, invalidator)); |
278 | + store.restoreItems(path); |
279 | + store.pruneDeleted(); |
280 | + readFiles(path, AllMedia); |
281 | + sw->addDir(path); |
282 | + volumes[path] = move(sw); |
283 | +} |
284 | + |
285 | +void VolumeManagerPrivate::removeVolume(const string& path) { |
286 | + assert(path[0] == '/'); |
287 | + if(volumes.find(path) == volumes.end()) |
288 | + return; |
289 | + store.archiveItems(path); |
290 | + volumes.erase(path); |
291 | +} |
292 | + |
293 | +void VolumeManagerPrivate::readFiles(const string &subdir, const MediaType type) { |
294 | + Scanner s(&extractor, subdir, type); |
295 | + MediaStoreTransaction txn = store.beginTransaction(); |
296 | + const int update_interval = 10; // How often to send invalidations. |
297 | + struct timespec previous_update, current_time; |
298 | + clock_gettime(CLOCK_MONOTONIC, &previous_update); |
299 | + previous_update.tv_sec -= update_interval/2; // Send the first update sooner for better visual appeal. |
300 | + while(true) { |
301 | + try { |
302 | + auto d = s.next(); |
303 | + clock_gettime(CLOCK_MONOTONIC, ¤t_time); |
304 | + while(g_main_context_pending(g_main_context_default())) { |
305 | + g_main_context_iteration(g_main_context_default(), FALSE); |
306 | + } |
307 | + if(current_time.tv_sec - previous_update.tv_sec >= update_interval) { |
308 | + txn.commit(); |
309 | + invalidator.invalidate(); |
310 | + previous_update = current_time; |
311 | + } |
312 | + // If the file is broken or unchanged, use fallback. |
313 | + if (store.is_broken_file(d.filename, d.etag)) { |
314 | + fprintf(stderr, "Using fallback data for unscannable file %s.\n", d.filename.c_str()); |
315 | + store.insert(extractor.fallback_extract(d)); |
316 | + continue; |
317 | + } |
318 | + if(d.etag == store.getETag(d.filename)) |
319 | + continue; |
320 | + |
321 | + try { |
322 | + store.insert_broken_file(d.filename, d.etag); |
323 | + MediaFile media; |
324 | + try { |
325 | + media = extractor.extract(d); |
326 | + } catch (const runtime_error &e) { |
327 | + fprintf(stderr, "Error extracting from '%s': %s\n", |
328 | + d.filename.c_str(), e.what()); |
329 | + media = extractor.fallback_extract(d); |
330 | + } |
331 | + store.insert(std::move(media)); |
332 | + } catch(const exception &e) { |
333 | + fprintf(stderr, "Error when indexing: %s\n", e.what()); |
334 | + } |
335 | + } catch(const StopIteration &stop) { |
336 | + break; |
337 | + } |
338 | + } |
339 | + txn.commit(); |
340 | +} |
341 | + |
342 | +} |
343 | |
344 | === added file 'src/daemon/VolumeManager.hh' |
345 | --- src/daemon/VolumeManager.hh 1970-01-01 00:00:00 +0000 |
346 | +++ src/daemon/VolumeManager.hh 2016-08-31 03:52:20 +0000 |
347 | @@ -0,0 +1,49 @@ |
348 | +/* |
349 | + * Copyright (C) 2016 Canonical, Ltd. |
350 | + * |
351 | + * Authors: |
352 | + * James Henstridge <james.henstridge@canonical.com> |
353 | + * |
354 | + * This library is free software; you can redistribute it and/or modify it under |
355 | + * the terms of version 3 of the GNU General Public License as published |
356 | + * by the Free Software Foundation. |
357 | + * |
358 | + * This library is distributed in the hope that it will be useful, but WITHOUT |
359 | + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
360 | + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
361 | + * details. |
362 | + * |
363 | + * You should have received a copy of the GNU General Public License |
364 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
365 | + */ |
366 | + |
367 | +#pragma once |
368 | + |
369 | +#include <memory> |
370 | +#include <string> |
371 | + |
372 | +namespace mediascanner { |
373 | + |
374 | +class MediaStore; |
375 | +class MetadataExtractor; |
376 | +class InvalidationSender; |
377 | + |
378 | +struct VolumeManagerPrivate; |
379 | + |
380 | +class VolumeManager final { |
381 | +public: |
382 | + VolumeManager(MediaStore& store, MetadataExtractor& extractor, InvalidationSender& invalidator); |
383 | + ~VolumeManager(); |
384 | + VolumeManager(const VolumeManager&) = delete; |
385 | + VolumeManager& operator=(const VolumeManager&) = delete; |
386 | + |
387 | + void queueAddVolume(const std::string& path); |
388 | + void queueRemoveVolume(const std::string& path); |
389 | + |
390 | + bool idle() const; |
391 | + |
392 | +private: |
393 | + std::unique_ptr<VolumeManagerPrivate> p; |
394 | +}; |
395 | + |
396 | +} |
397 | |
398 | === modified file 'src/daemon/scannerdaemon.cc' |
399 | --- src/daemon/scannerdaemon.cc 2016-03-17 02:11:17 +0000 |
400 | +++ src/daemon/scannerdaemon.cc 2016-08-31 03:52:20 +0000 |
401 | @@ -32,13 +32,10 @@ |
402 | |
403 | #include "../mediascanner/MediaFile.hh" |
404 | #include "../mediascanner/MediaStore.hh" |
405 | -#include "../extractor/DetectedFile.hh" |
406 | #include "../extractor/MetadataExtractor.hh" |
407 | #include "MountWatcher.hh" |
408 | -#include "SubtreeWatcher.hh" |
409 | -#include "Scanner.hh" |
410 | #include "InvalidationSender.hh" |
411 | -#include "../mediascanner/internal/utils.hh" |
412 | +#include "VolumeManager.hh" |
413 | |
414 | using namespace std; |
415 | |
416 | @@ -74,9 +71,6 @@ |
417 | void setupBus(); |
418 | void setupSignals(); |
419 | void setupMountWatcher(); |
420 | - void readFiles(MediaStore &store, const string &subdir, const MediaType type); |
421 | - void addDir(const string &dir); |
422 | - void removeDir(const string &dir); |
423 | static gboolean signalCallback(gpointer data); |
424 | static void busNameLostCallback(GDBusConnection *connection, const char *name, gpointer data); |
425 | void mountEvent(const MountWatcher::Info &info); |
426 | @@ -86,8 +80,8 @@ |
427 | string cachedir; |
428 | unique_ptr<MediaStore> store; |
429 | unique_ptr<MetadataExtractor> extractor; |
430 | - map<string, unique_ptr<SubtreeWatcher>> subtrees; |
431 | InvalidationSender invalidator; |
432 | + unique_ptr<VolumeManager> volumes; |
433 | unique_ptr<GMainLoop,void(*)(GMainLoop*)> main_loop; |
434 | unique_ptr<GDBusConnection,void(*)(void*)> session_bus; |
435 | unsigned int bus_name_id = 0; |
436 | @@ -99,6 +93,7 @@ |
437 | setupBus(); |
438 | store.reset(new MediaStore(MS_READ_WRITE, "/media/")); |
439 | extractor.reset(new MetadataExtractor(session_bus.get())); |
440 | + volumes.reset(new VolumeManager(*store, *extractor, invalidator)); |
441 | |
442 | setupMountWatcher(); |
443 | |
444 | @@ -111,13 +106,13 @@ |
445 | // it falls back to home directory. This would mean scanning the entire home |
446 | // directory. This is probably not what people want so skip if this is the case. |
447 | if (musicdir && !is_same_directory(musicdir, homedir)) |
448 | - addDir(musicdir); |
449 | + volumes->queueAddVolume(musicdir); |
450 | |
451 | if (videodir && !is_same_directory(videodir, homedir)) |
452 | - addDir(videodir); |
453 | + volumes->queueAddVolume(videodir); |
454 | |
455 | if (picturesdir && !is_same_directory(picturesdir, homedir)) |
456 | - addDir(picturesdir); |
457 | + volumes->queueAddVolume(picturesdir); |
458 | |
459 | // In case someone opened the db file before we could populate it. |
460 | invalidator.invalidate(); |
461 | @@ -178,89 +173,6 @@ |
462 | sigterm_id = g_unix_signal_add(SIGTERM, &ScannerDaemon::signalCallback, this); |
463 | } |
464 | |
465 | -void ScannerDaemon::addDir(const string &dir) { |
466 | - assert(dir[0] == '/'); |
467 | - if(subtrees.find(dir) != subtrees.end()) { |
468 | - return; |
469 | - } |
470 | - if(is_rootlike(dir)) { |
471 | - fprintf(stderr, "Directory %s looks like a top level root directory, skipping it (%s).\n", |
472 | - dir.c_str(), __PRETTY_FUNCTION__); |
473 | - return; |
474 | - } |
475 | - if(is_optical_disc(dir)) { |
476 | - fprintf(stderr, "Directory %s looks like an optical disc, skipping it.\n", dir.c_str()); |
477 | - return; |
478 | - } |
479 | - if(has_scanblock(dir)) { |
480 | - fprintf(stderr, "Directory %s has a scan block file, skipping it.\n", dir.c_str()); |
481 | - return; |
482 | - } |
483 | - unique_ptr<SubtreeWatcher> sw(new SubtreeWatcher(*store.get(), *extractor.get(), invalidator)); |
484 | - store->restoreItems(dir); |
485 | - store->pruneDeleted(); |
486 | - readFiles(*store.get(), dir, AllMedia); |
487 | - sw->addDir(dir); |
488 | - subtrees[dir] = move(sw); |
489 | -} |
490 | - |
491 | -void ScannerDaemon::removeDir(const string &dir) { |
492 | - assert(dir[0] == '/'); |
493 | - if(subtrees.find(dir) == subtrees.end()) |
494 | - return; |
495 | - store->archiveItems(dir); |
496 | - subtrees.erase(dir); |
497 | -} |
498 | - |
499 | -void ScannerDaemon::readFiles(MediaStore &store, const string &subdir, const MediaType type) { |
500 | - Scanner s(extractor.get(), subdir, type); |
501 | - MediaStoreTransaction txn = store.beginTransaction(); |
502 | - const int update_interval = 10; // How often to send invalidations. |
503 | - struct timespec previous_update, current_time; |
504 | - clock_gettime(CLOCK_MONOTONIC, &previous_update); |
505 | - previous_update.tv_sec -= update_interval/2; // Send the first update sooner for better visual appeal. |
506 | - while(true) { |
507 | - try { |
508 | - auto d = s.next(); |
509 | - clock_gettime(CLOCK_MONOTONIC, ¤t_time); |
510 | - while(g_main_context_pending(g_main_context_default())) { |
511 | - g_main_context_iteration(g_main_context_default(), FALSE); |
512 | - } |
513 | - if(current_time.tv_sec - previous_update.tv_sec >= update_interval) { |
514 | - txn.commit(); |
515 | - invalidator.invalidate(); |
516 | - previous_update = current_time; |
517 | - } |
518 | - // If the file is broken or unchanged, use fallback. |
519 | - if (store.is_broken_file(d.filename, d.etag)) { |
520 | - fprintf(stderr, "Using fallback data for unscannable file %s.\n", d.filename.c_str()); |
521 | - store.insert(extractor->fallback_extract(d)); |
522 | - continue; |
523 | - } |
524 | - if(d.etag == store.getETag(d.filename)) |
525 | - continue; |
526 | - |
527 | - try { |
528 | - store.insert_broken_file(d.filename, d.etag); |
529 | - MediaFile media; |
530 | - try { |
531 | - media = extractor->extract(d); |
532 | - } catch (const runtime_error &e) { |
533 | - fprintf(stderr, "Error extracting from '%s': %s\n", |
534 | - d.filename.c_str(), e.what()); |
535 | - media = extractor->fallback_extract(d); |
536 | - } |
537 | - store.insert(std::move(media)); |
538 | - } catch(const exception &e) { |
539 | - fprintf(stderr, "Error when indexing: %s\n", e.what()); |
540 | - } |
541 | - } catch(const StopIteration &stop) { |
542 | - break; |
543 | - } |
544 | - } |
545 | - txn.commit(); |
546 | -} |
547 | - |
548 | int ScannerDaemon::run() { |
549 | g_main_loop_run(main_loop.get()); |
550 | return 99; |
551 | @@ -279,25 +191,14 @@ |
552 | } |
553 | |
554 | void ScannerDaemon::mountEvent(const MountWatcher::Info& info) { |
555 | - bool changed = false; |
556 | if (info.is_mounted) { |
557 | printf("Volume %s was mounted.\n", info.mount_point.c_str()); |
558 | if (info.mount_point.substr(0, 6) == "/media") { |
559 | - addDir(info.mount_point); |
560 | - changed = true; |
561 | + volumes->queueAddVolume(info.mount_point); |
562 | } |
563 | } else { |
564 | printf("Volume %s was unmounted.\n", info.mount_point.c_str()); |
565 | - if (subtrees.find(info.mount_point) != subtrees.end()) { |
566 | - removeDir(info.mount_point); |
567 | - changed = true; |
568 | - } else { |
569 | - // This volume was not tracked because it looked rootlike. |
570 | - // Thus we don't need to do anything. |
571 | - } |
572 | - } |
573 | - if (changed) { |
574 | - invalidator.invalidate(); |
575 | + volumes->queueRemoveVolume(info.mount_point); |
576 | } |
577 | } |
578 | |
579 | |
580 | === modified file 'test/CMakeLists.txt' |
581 | --- test/CMakeLists.txt 2016-02-25 01:53:30 +0000 |
582 | +++ test/CMakeLists.txt 2016-08-31 03:52:20 +0000 |
583 | @@ -60,6 +60,13 @@ |
584 | set_tests_properties(test_subtreewatcher PROPERTIES |
585 | ENVIRONMENT "GIO_MODULE_DIR=${CMAKE_CURRENT_BINARY_DIR}/modules") |
586 | |
587 | +add_executable(test_volumemanager test_volumemanager.cc) |
588 | +target_link_libraries(test_volumemanager mediascanner scannerstuff gtest) |
589 | +add_test(test_volumemanager test_volumemanager) |
590 | +# The gvfs modules interfere with the private D-Bus test fixtures |
591 | +set_tests_properties(test_volumemanager PROPERTIES |
592 | + ENVIRONMENT "GIO_MODULE_DIR=${CMAKE_CURRENT_BINARY_DIR}/modules") |
593 | + |
594 | add_executable(test_sqliteutils test_sqliteutils.cc) |
595 | target_link_libraries(test_sqliteutils gtest ${MEDIASCANNER_DEPS_LDFLAGS}) |
596 | add_test(test_sqliteutils test_sqliteutils) |
597 | |
598 | === added file 'test/test_volumemanager.cc' |
599 | --- test/test_volumemanager.cc 1970-01-01 00:00:00 +0000 |
600 | +++ test/test_volumemanager.cc 2016-08-31 03:52:20 +0000 |
601 | @@ -0,0 +1,234 @@ |
602 | +/* |
603 | + * Copyright (C) 2016 Canonical, Ltd. |
604 | + * |
605 | + * Authors: |
606 | + * James Henstridge <james.henstridge@canonical.com> |
607 | + * |
608 | + * This library is free software; you can redistribute it and/or modify it under |
609 | + * the terms of version 3 of the GNU General Public License as published |
610 | + * by the Free Software Foundation. |
611 | + * |
612 | + * This library is distributed in the hope that it will be useful, but WITHOUT |
613 | + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
614 | + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
615 | + * details. |
616 | + * |
617 | + * You should have received a copy of the GNU General Public License |
618 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
619 | + */ |
620 | + |
621 | +#include <mediascanner/MediaFile.hh> |
622 | +#include <mediascanner/MediaStore.hh> |
623 | +#include <extractor/MetadataExtractor.hh> |
624 | +#include <daemon/InvalidationSender.hh> |
625 | +#include <daemon/VolumeManager.hh> |
626 | + |
627 | +#include "test_config.h" |
628 | + |
629 | +#include <fcntl.h> |
630 | +#include <algorithm> |
631 | +#include <cstdio> |
632 | +#include <memory> |
633 | +#include <stdexcept> |
634 | +#include <string> |
635 | + |
636 | +#include <gio/gio.h> |
637 | +#include <gtest/gtest.h> |
638 | + |
639 | +using namespace std; |
640 | +using namespace mediascanner; |
641 | + |
642 | +namespace { |
643 | + |
644 | +typedef std::unique_ptr<GDBusConnection, decltype(&g_object_unref)> GDBusConnectionPtr; |
645 | + |
646 | +void copy_file(const string &src, const string &dst) { |
647 | + FILE* f = fopen(src.c_str(), "r"); |
648 | + ASSERT_TRUE(f); |
649 | + fseek(f, 0, SEEK_END); |
650 | + size_t size = ftell(f); |
651 | + |
652 | + char* buf = new char[size]; |
653 | + |
654 | + fseek(f, 0, SEEK_SET); |
655 | + ASSERT_EQ(fread(buf, 1, size, f), size); |
656 | + fclose(f); |
657 | + |
658 | + f = fopen(dst.c_str(), "w"); |
659 | + ASSERT_TRUE(f); |
660 | + ASSERT_EQ(fwrite(buf, 1, size, f), size); |
661 | + fclose(f); |
662 | + delete[] buf; |
663 | +} |
664 | + |
665 | +} |
666 | + |
667 | +class VolumeManagerTest : public ::testing::Test { |
668 | +protected: |
669 | + virtual void SetUp() override { |
670 | + main_loop_.reset(g_main_loop_new(nullptr, false)); |
671 | + |
672 | + tmpdir_ = TEST_DIR "/volumemanager-test.XXXXXX"; |
673 | + ASSERT_NE(nullptr, mkdtemp(&tmpdir_[0])); |
674 | + |
675 | + test_dbus_.reset(g_test_dbus_new(G_TEST_DBUS_NONE)); |
676 | + g_test_dbus_add_service_dir(test_dbus_.get(), TEST_DIR "/services"); |
677 | + g_test_dbus_up(test_dbus_.get()); |
678 | + session_bus_ = make_connection(); |
679 | + |
680 | + store_.reset(new MediaStore(":memory:", MS_READ_WRITE)); |
681 | + extractor_.reset(new MetadataExtractor(session_bus_.get())); |
682 | + invalidator_.reset(new InvalidationSender); |
683 | + volumes_.reset(new VolumeManager(*store_, *extractor_, *invalidator_)); |
684 | + } |
685 | + |
686 | + virtual void TearDown() override { |
687 | + volumes_.reset(); |
688 | + invalidator_.reset(); |
689 | + extractor_.reset(); |
690 | + store_.reset(); |
691 | + |
692 | + session_bus_.reset(); |
693 | + test_dbus_.reset(); |
694 | + |
695 | + if (!tmpdir_.empty()) { |
696 | + string cmd = "rm -rf " + tmpdir_; |
697 | + ASSERT_EQ(0, system(cmd.c_str())); |
698 | + } |
699 | + } |
700 | + |
701 | + GDBusConnectionPtr make_connection() { |
702 | + GError *error = nullptr; |
703 | + char *address = g_dbus_address_get_for_bus_sync(G_BUS_TYPE_SESSION, nullptr, &error); |
704 | + if (!address) { |
705 | + string errortxt(error->message); |
706 | + g_error_free(error); |
707 | + throw std::runtime_error( |
708 | + string("Failed to determine session bus address: ") + errortxt); |
709 | + } |
710 | + GDBusConnectionPtr bus( |
711 | + g_dbus_connection_new_for_address_sync( |
712 | + address, static_cast<GDBusConnectionFlags>(G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION), nullptr, nullptr, &error), |
713 | + g_object_unref); |
714 | + g_free(address); |
715 | + if (!bus) { |
716 | + string errortxt(error->message); |
717 | + g_error_free(error); |
718 | + throw std::runtime_error( |
719 | + string("Failed to connect to session bus: ") + errortxt); |
720 | + } |
721 | + return std::move(bus); |
722 | + } |
723 | + |
724 | + void wait_until_idle() { |
725 | + while (g_main_context_iteration(nullptr, FALSE)) { |
726 | + if (volumes_->idle()) { |
727 | + break; |
728 | + } |
729 | + } |
730 | + } |
731 | + |
732 | + string tmpdir_; |
733 | + unique_ptr<GTestDBus,decltype(&g_object_unref)> test_dbus_ {nullptr, g_object_unref}; |
734 | + unique_ptr<MediaStore> store_; |
735 | + unique_ptr<VolumeManager> volumes_; |
736 | + |
737 | +private: |
738 | + GDBusConnectionPtr session_bus_ {nullptr, g_object_unref}; |
739 | + unique_ptr<MetadataExtractor> extractor_; |
740 | + unique_ptr<InvalidationSender> invalidator_; |
741 | + |
742 | + unique_ptr<GMainLoop, decltype(&g_main_loop_unref)> main_loop_ {nullptr, g_main_loop_unref}; |
743 | +}; |
744 | + |
745 | +TEST_F(VolumeManagerTest, add_remove_volumes) |
746 | +{ |
747 | + const string volume1 = tmpdir_ + "/volume1"; |
748 | + const string volume2 = tmpdir_ + "/volume2"; |
749 | + ASSERT_EQ(0, mkdir(volume1.c_str(), 0755)); |
750 | + ASSERT_EQ(0, mkdir(volume2.c_str(), 0755)); |
751 | + |
752 | + const string file1 = volume1 + "/file1.ogg"; |
753 | + const string file2 = volume1 + "/file2.ogg"; |
754 | + const string file3 = volume2 + "/file3.ogg"; |
755 | + |
756 | + copy_file(SOURCE_DIR "/media/testfile.ogg", file1); |
757 | + copy_file(SOURCE_DIR "/media/testfile.ogg", file2); |
758 | + copy_file(SOURCE_DIR "/media/testfile.ogg", file3); |
759 | + |
760 | + volumes_->queueAddVolume(volume1); |
761 | + wait_until_idle(); |
762 | + EXPECT_EQ(store_->size(), 2); |
763 | + store_->lookup(file1); |
764 | + store_->lookup(file2); |
765 | + try { |
766 | + store_->lookup(file3); |
767 | + FAIL(); |
768 | + } catch (const runtime_error&) { |
769 | + } |
770 | + |
771 | + // Queue up two operations |
772 | + volumes_->queueAddVolume(volume2); |
773 | + volumes_->queueRemoveVolume(volume1); |
774 | + wait_until_idle(); |
775 | + EXPECT_EQ(store_->size(), 1); |
776 | + store_->lookup(file3); |
777 | + try { |
778 | + store_->lookup(file1); |
779 | + store_->lookup(file2); |
780 | + } catch (const runtime_error&) { |
781 | + } |
782 | + |
783 | + volumes_->queueRemoveVolume(volume2); |
784 | + wait_until_idle(); |
785 | + EXPECT_EQ(store_->size(), 0); |
786 | + |
787 | + // This should result in only volume2 being added |
788 | + volumes_->queueAddVolume(volume1); |
789 | + volumes_->queueAddVolume(volume2); |
790 | + volumes_->queueRemoveVolume(volume1); |
791 | + wait_until_idle(); |
792 | + EXPECT_EQ(store_->size(), 1); |
793 | + store_->lookup(file3); |
794 | +} |
795 | + |
796 | +TEST_F(VolumeManagerTest, add_volume_during_initial_scan) |
797 | +{ |
798 | + const string volume1 = tmpdir_ + "/volume1"; |
799 | + const string volume2 = tmpdir_ + "/volume2"; |
800 | + ASSERT_EQ(0, mkdir(volume1.c_str(), 0755)); |
801 | + ASSERT_EQ(0, mkdir(volume2.c_str(), 0755)); |
802 | + |
803 | + const string file1 = volume1 + "/file1.ogg"; |
804 | + const string file2 = volume1 + "/file2.ogg"; |
805 | + const string file3 = volume2 + "/file3.ogg"; |
806 | + |
807 | + copy_file(SOURCE_DIR "/media/testfile.ogg", file1); |
808 | + copy_file(SOURCE_DIR "/media/testfile.ogg", file2); |
809 | + copy_file(SOURCE_DIR "/media/testfile.ogg", file3); |
810 | + |
811 | + // Set an idle function that should be called during the initial |
812 | + // scan that will queue up adding a second volume. |
813 | + function<void()> callback = [&] { |
814 | + // We expect that this is called during the scan |
815 | + EXPECT_FALSE(volumes_->idle()); |
816 | + volumes_->queueAddVolume(volume2); |
817 | + }; |
818 | + g_idle_add([](void *user_data) -> gboolean { |
819 | + auto callback = *reinterpret_cast<function<void()>*>(user_data); |
820 | + callback(); |
821 | + return G_SOURCE_REMOVE; |
822 | + }, &callback); |
823 | + |
824 | + volumes_->queueAddVolume(volume1); |
825 | + wait_until_idle(); |
826 | + EXPECT_EQ(store_->size(), 3); |
827 | + store_->lookup(file1); |
828 | + store_->lookup(file2); |
829 | + store_->lookup(file3); |
830 | +} |
831 | + |
832 | +int main(int argc, char **argv) { |
833 | + ::testing::InitGoogleTest(&argc, argv); |
834 | + return RUN_ALL_TESTS(); |
835 | +} |
FAILED: Continuous integration, rev:339 /code.launchpad .net/~jamesh/ mediascanner2/ bug-1489656/ +merge/ 304350/ +edit-commit- message
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https:/
https:/ /jenkins. canonical. com/unity- api-1/job/ lp-mediascanner 2-ci/5/ /jenkins. canonical. com/unity- api-1/job/ build/527/ console /jenkins. canonical. com/unity- api-1/job/ build-0- fetch/533 /jenkins. canonical. com/unity- api-1/job/ build-1- sourcepkg/ release= vivid+overlay/ 435 /jenkins. canonical. com/unity- api-1/job/ build-1- sourcepkg/ release= xenial+ overlay/ 435 /jenkins. canonical. com/unity- api-1/job/ build-1- sourcepkg/ release= yakkety/ 435 /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=amd64, release= vivid+overlay/ 365 /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=amd64, release= vivid+overlay/ 365/artifact/ output/ *zip*/output. zip /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=amd64, release= xenial+ overlay/ 365 /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=amd64, release= xenial+ overlay/ 365/artifact/ output/ *zip*/output. zip /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=amd64, release= yakkety/ 365 /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=amd64, release= yakkety/ 365/artifact/ output/ *zip*/output. zip /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=armhf, release= vivid+overlay/ 365 /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=armhf, release= vivid+overlay/ 365/artifact/ output/ *zip*/output. zip /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=armhf, release= xenial+ overlay/ 365 /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=armhf, release= xenial+ overlay/ 365/artifact/ output/ *zip*/output. zip /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=armhf, release= yakkety/ 365/console /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=i386, release= vivid+overlay/ 365 /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=i386, release= vivid+overlay/ 365/artifact/ output/ *zip*/output. zip /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=i386, release= xenial+ overlay/ 365 /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=i386, release= xenial+ overlay/ 365/artifact/ output/ *zip*/output. zip /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=i386, release= yakkety/ 365 /jenkins. canonical. com/unity- api-1/job/ build-2- binpkg/ arch=i386, release= yakkety/ 365/artifact/ output/ *zip*/output. zip
Executed test runs:
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
FAILURE: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
Click here to trigger a rebuild: /jenkins. canonical. com/unity- api-1/job/ lp-mediascanner 2-ci/5/ rebuild
https:/