Merge lp:~jamesh/mediascanner2/no-notify-on-unchanged-close into lp:mediascanner2

Proposed by James Henstridge
Status: Merged
Approved by: Michi Henning
Approved revision: 323
Merged at revision: 324
Proposed branch: lp:~jamesh/mediascanner2/no-notify-on-unchanged-close
Merge into: lp:mediascanner2
Diff against target: 285 lines (+215/-4)
5 files modified
CMakeLists.txt (+1/-0)
src/daemon/SubtreeWatcher.cc (+5/-3)
src/daemon/SubtreeWatcher.hh (+1/-1)
test/CMakeLists.txt (+7/-0)
test/test_subtreewatcher.cc (+201/-0)
To merge this branch: bzr merge lp:~jamesh/mediascanner2/no-notify-on-unchanged-close
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Needs Fixing
Michi Henning (community) Approve
Review via email: mp+285446@code.launchpad.net

Commit message

Don't emit the InvalidateResults signal if a file was opened for writing and then closed, but not actually modified

Description of the change

Don't emit the InvalidateResults signal if a file was opened for writing and then closed, but not actually modified.

This prevents Michi's thumbnailer taglib branch from triggering lots of invalidations, since TagLib opens files for writing for no good reason.

The sleeps in the new test are needed because the invalidation code tries to batch changes to reduce the number of invalidations. If I add more tests to this fixture, I'll definitely want to get rid of the sleeps, but for now it is probably okay.

To post a comment you must log in.
Revision history for this message
Michi Henning (michihenning) wrote :

I installed this on the phone and verified that the it fixes the problem with the taglib branch. James, thanks heaps for helping with this in record time!

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)

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 2015-12-16 07:07:41 +0000
3+++ CMakeLists.txt 2016-02-10 06:53:14 +0000
4@@ -91,6 +91,7 @@
5 test_extractorbackend
6 test_sqliteutils
7 test_mfbuilder
8+ test_subtreewatcher
9 test_dbus
10 test_qml
11 test_util
12
13=== modified file 'src/daemon/SubtreeWatcher.cc'
14--- src/daemon/SubtreeWatcher.cc 2015-12-16 06:50:24 +0000
15+++ src/daemon/SubtreeWatcher.cc 2016-02-10 06:53:14 +0000
16@@ -168,7 +168,8 @@
17 return true;
18 }
19
20-void SubtreeWatcher::fileAdded(const string &abspath) {
21+bool SubtreeWatcher::fileAdded(const string &abspath) {
22+ bool changed = false;
23 printf("New file was created: %s.\n", abspath.c_str());
24 try {
25 DetectedFile d = p->extractor.detect(abspath);
26@@ -182,10 +183,12 @@
27 // and the next time this file is encountered, it is skipped.
28 // Insert cleans broken status of the file.
29 p->store.insert(p->extractor.extract(d));
30+ changed = true;
31 }
32 } catch(const exception &e) {
33 fprintf(stderr, "Error when adding new file: %s\n", e.what());
34 }
35+ return changed;
36 }
37
38 void SubtreeWatcher::fileDeleted(const string &abspath) {
39@@ -250,8 +253,7 @@
40 changed = true;
41 }
42 if(is_file) {
43- fileAdded(abspath);
44- changed = true;
45+ changed = fileAdded(abspath);
46 }
47 } else if((event->mask & IN_DELETE) || (event->mask & IN_MOVED_FROM)) {
48 if(p->str2wd.find(abspath) != p->str2wd.end()) {
49
50=== modified file 'src/daemon/SubtreeWatcher.hh'
51--- src/daemon/SubtreeWatcher.hh 2014-07-16 09:05:29 +0000
52+++ src/daemon/SubtreeWatcher.hh 2016-02-10 06:53:14 +0000
53@@ -35,7 +35,7 @@
54 private:
55
56 SubtreeWatcherPrivate *p;
57- void fileAdded(const std::string &abspath);
58+ bool fileAdded(const std::string &abspath);
59 void fileDeleted(const std::string &abspath);
60 void dirAdded(const std::string &abspath);
61 void dirRemoved(const std::string &abspath);
62
63=== modified file 'test/CMakeLists.txt'
64--- test/CMakeLists.txt 2015-12-11 06:34:01 +0000
65+++ test/CMakeLists.txt 2016-02-10 06:53:14 +0000
66@@ -53,6 +53,13 @@
67 set_tests_properties(test_metadataextractor PROPERTIES
68 ENVIRONMENT "GIO_MODULE_DIR=${CMAKE_CURRENT_BINARY_DIR}/modules")
69
70+add_executable(test_subtreewatcher test_subtreewatcher.cc)
71+target_link_libraries(test_subtreewatcher mediascanner scannerstuff gtest)
72+add_test(test_subtreewatcher test_subtreewatcher)
73+# The gvfs modules interfere with the private D-Bus test fixtures
74+set_tests_properties(test_subtreewatcher PROPERTIES
75+ ENVIRONMENT "GIO_MODULE_DIR=${CMAKE_CURRENT_BINARY_DIR}/modules")
76+
77 add_executable(test_sqliteutils test_sqliteutils.cc)
78 target_link_libraries(test_sqliteutils gtest ${MEDIASCANNER_DEPS_LDFLAGS})
79 add_test(test_sqliteutils test_sqliteutils)
80
81=== added file 'test/test_subtreewatcher.cc'
82--- test/test_subtreewatcher.cc 1970-01-01 00:00:00 +0000
83+++ test/test_subtreewatcher.cc 2016-02-10 06:53:14 +0000
84@@ -0,0 +1,201 @@
85+/*
86+ * Copyright (C) 2016 Canonical, Ltd.
87+ *
88+ * Authors:
89+ * James Henstridge <james.henstridge@canonical.com>
90+ *
91+ * This library is free software; you can redistribute it and/or modify it under
92+ * the terms of version 3 of the GNU General Public License as published
93+ * by the Free Software Foundation.
94+ *
95+ * This library is distributed in the hope that it will be useful, but WITHOUT
96+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
97+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
98+ * details.
99+ *
100+ * You should have received a copy of the GNU General Public License
101+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
102+ */
103+
104+#include <mediascanner/MediaFile.hh>
105+#include <mediascanner/MediaStore.hh>
106+#include <extractor/MetadataExtractor.hh>
107+#include <daemon/InvalidationSender.hh>
108+#include <daemon/SubtreeWatcher.hh>
109+
110+#include "test_config.h"
111+
112+#include <fcntl.h>
113+#include <algorithm>
114+#include <cstdio>
115+#include <memory>
116+#include <stdexcept>
117+#include <string>
118+
119+#include <gio/gio.h>
120+#include <gtest/gtest.h>
121+
122+using namespace std;
123+using namespace mediascanner;
124+
125+namespace {
126+
127+typedef std::unique_ptr<GDBusConnection, decltype(&g_object_unref)> GDBusConnectionPtr;
128+
129+void copy_file(const string &src, const string &dst) {
130+ FILE* f = fopen(src.c_str(), "r");
131+ ASSERT_TRUE(f);
132+ fseek(f, 0, SEEK_END);
133+ size_t size = ftell(f);
134+
135+ char* buf = new char[size];
136+
137+ fseek(f, 0, SEEK_SET);
138+ ASSERT_EQ(fread(buf, 1, size, f), size);
139+ fclose(f);
140+
141+ f = fopen(dst.c_str(), "w");
142+ ASSERT_TRUE(f);
143+ ASSERT_EQ(fwrite(buf, 1, size, f), size);
144+ fclose(f);
145+ delete[] buf;
146+}
147+
148+void iterate_main_loop() {
149+ while (g_main_context_iteration(nullptr, FALSE)) {
150+ }
151+}
152+
153+void sleep(int seconds) {
154+ unique_ptr<GMainLoop, decltype(&g_main_loop_unref)> main_loop(
155+ g_main_loop_new(nullptr, false), g_main_loop_unref);
156+ g_timeout_add_seconds(seconds, [](void *user_data) -> int {
157+ auto main_loop = reinterpret_cast<GMainLoop*>(user_data);
158+ g_main_loop_quit(main_loop);
159+ return G_SOURCE_REMOVE;
160+ }, main_loop.get());
161+ g_main_loop_run(main_loop.get());
162+}
163+
164+}
165+
166+class SubtreeWatcherTest : public ::testing::Test {
167+protected:
168+ virtual void SetUp() override {
169+ tmpdir_ = TEST_DIR "/subtreewatcher-test.XXXXXX";
170+ ASSERT_NE(nullptr, mkdtemp(&tmpdir_[0]));
171+
172+ test_dbus_.reset(g_test_dbus_new(G_TEST_DBUS_NONE));
173+ g_test_dbus_add_service_dir(test_dbus_.get(), TEST_DIR "/services");
174+ g_test_dbus_up(test_dbus_.get());
175+ session_bus_ = make_connection();
176+
177+ g_dbus_connection_signal_subscribe(
178+ session_bus_.get(), nullptr,
179+ "com.canonical.unity.scopes", "InvalidateResults",
180+ "/com/canonical/unity/scopes",
181+ "mediascanner-music", G_DBUS_SIGNAL_FLAGS_NONE,
182+ &SubtreeWatcherTest::invalidateCallback, this, nullptr);
183+
184+ store_.reset(new MediaStore(":memory:", MS_READ_WRITE));
185+ extractor_.reset(new MetadataExtractor(session_bus_.get()));
186+ invalidator_.reset(new InvalidationSender);
187+ invalidator_->setBus(session_bus_.get());
188+ watcher_.reset(new SubtreeWatcher(*store_, *extractor_, *invalidator_));
189+ }
190+
191+ virtual void TearDown() override {
192+ watcher_.reset();
193+ invalidator_.reset();
194+ extractor_.reset();
195+ store_.reset();
196+
197+ session_bus_.reset();
198+ test_dbus_.reset();
199+
200+ if (!tmpdir_.empty()) {
201+ string cmd = "rm -rf " + tmpdir_;
202+ ASSERT_EQ(0, system(cmd.c_str()));
203+ }
204+ }
205+
206+ GDBusConnectionPtr make_connection() {
207+ GError *error = nullptr;
208+ char *address = g_dbus_address_get_for_bus_sync(G_BUS_TYPE_SESSION, nullptr, &error);
209+ if (!address) {
210+ string errortxt(error->message);
211+ g_error_free(error);
212+ throw std::runtime_error(
213+ string("Failed to determine session bus address: ") + errortxt);
214+ }
215+ GDBusConnectionPtr bus(
216+ g_dbus_connection_new_for_address_sync(
217+ address, static_cast<GDBusConnectionFlags>(G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION), nullptr, nullptr, &error),
218+ g_object_unref);
219+ g_free(address);
220+ if (!bus) {
221+ string errortxt(error->message);
222+ g_error_free(error);
223+ throw std::runtime_error(
224+ string("Failed to connect to session bus: ") + errortxt);
225+ }
226+ return std::move(bus);
227+ }
228+
229+ static void invalidateCallback(GDBusConnection */*connection*/,
230+ const char */*sender_name*/,
231+ const char */*object_path*/,
232+ const char */*interface_name*/,
233+ const char */*signal_name*/,
234+ GVariant */*parameters*/,
235+ gpointer user_data) {
236+ auto test = reinterpret_cast<SubtreeWatcherTest*>(user_data);
237+ test->invalidate_count_++;
238+ }
239+
240+ string tmpdir_;
241+ unique_ptr<GTestDBus,decltype(&g_object_unref)> test_dbus_ {nullptr, g_object_unref};
242+ GDBusConnectionPtr session_bus_ {nullptr, g_object_unref};
243+ unique_ptr<MediaStore> store_;
244+ unique_ptr<MetadataExtractor> extractor_;
245+ unique_ptr<InvalidationSender> invalidator_;
246+ unique_ptr<SubtreeWatcher> watcher_;
247+
248+ int invalidate_count_ = 0;
249+};
250+
251+TEST_F(SubtreeWatcherTest, open_for_write_without_change)
252+{
253+ watcher_->addDir(tmpdir_);
254+ iterate_main_loop();
255+
256+ string testfile = tmpdir_ + "/testfile.ogg";
257+ copy_file(SOURCE_DIR "/media/testfile.ogg", testfile);
258+ sleep(2);
259+ iterate_main_loop();
260+ ASSERT_EQ(store_->size(), 1);
261+ // Invalidate called once for new file.
262+ EXPECT_EQ(1, invalidate_count_);
263+
264+ int fd = open(testfile.c_str(), O_RDWR);
265+ ASSERT_GE(fd, 0);
266+ ASSERT_EQ(0, close(fd));
267+ sleep(2);
268+ iterate_main_loop();
269+ // No change, to file so no new invalidations
270+ EXPECT_EQ(1, invalidate_count_);
271+
272+ fd = open(testfile.c_str(), O_RDWR|O_APPEND);
273+ ASSERT_GE(fd, 0);
274+ ASSERT_EQ(5, write(fd, "hello", 5));
275+ ASSERT_EQ(0, close(fd));
276+ sleep(2);
277+ iterate_main_loop();
278+ // File changed, so invalidation count increases.
279+ EXPECT_EQ(2, invalidate_count_);
280+}
281+
282+int main(int argc, char **argv) {
283+ ::testing::InitGoogleTest(&argc, argv);
284+ return RUN_ALL_TESTS();
285+}

Subscribers

People subscribed via source and target branches