Merge lp:~marcustomlinson/unity-scopes-api/reg_file_system_monitor into lp:unity-scopes-api/devel

Proposed by Marcus Tomlinson
Status: Merged
Approved by: Michi Henning
Approved revision: 424
Merged at revision: 358
Proposed branch: lp:~marcustomlinson/unity-scopes-api/reg_file_system_monitor
Merge into: lp:unity-scopes-api/devel
Diff against target: 1716 lines (+1057/-151)
29 files modified
debian/changelog (+10/-0)
debian/libunity-scopes1.symbols (+1/-1)
include/unity/scopes/Registry.h (+9/-0)
include/unity/scopes/internal/MWRegistry.h (+8/-0)
include/unity/scopes/internal/RegistryImpl.h (+1/-0)
include/unity/scopes/internal/RegistryObject.h (+6/-1)
include/unity/scopes/internal/zmq_middleware/ZmqSubscriber.h (+1/-1)
include/unity/scopes/testing/MockRegistry.h (+1/-0)
scoperegistry/CMakeLists.txt (+2/-0)
scoperegistry/DirWatcher.cpp (+272/-0)
scoperegistry/DirWatcher.h (+79/-0)
scoperegistry/FindFiles.cpp (+35/-23)
scoperegistry/FindFiles.h (+17/-9)
scoperegistry/ScopesWatcher.cpp (+184/-0)
scoperegistry/ScopesWatcher.h (+57/-0)
scoperegistry/scoperegistry.cpp (+136/-100)
src/scopes/internal/MWRegistry.cpp (+24/-1)
src/scopes/internal/RegistryImpl.cpp (+5/-0)
src/scopes/internal/RegistryObject.cpp (+32/-2)
src/scopes/internal/zmq_middleware/ZmqSubscriber.cpp (+11/-4)
test/gtest/scopes/Registry/CMakeLists.txt (+1/-0)
test/gtest/scopes/Registry/Registry_test.cpp (+138/-2)
test/gtest/scopes/Registry/other_scopes/CMakeLists.txt (+2/-0)
test/gtest/scopes/Registry/other_scopes/testscopeC/CMakeLists.txt (+1/-0)
test/gtest/scopes/Registry/other_scopes/testscopeC/testscopeC.ini.in (+8/-0)
test/gtest/scopes/Registry/other_scopes/testscopeD/CMakeLists.txt (+1/-0)
test/gtest/scopes/Registry/other_scopes/testscopeD/testscopeD.ini.in (+8/-0)
test/gtest/scopes/internal/RegistryObject/RegistryObject_test.cpp (+1/-1)
test/gtest/scopes/internal/zmq_middleware/RegistryI/RegistryI_test.cpp (+6/-6)
To merge this branch: bzr merge lp:~marcustomlinson/unity-scopes-api/reg_file_system_monitor
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
Michi Henning (community) Approve
Review via email: mp+220501@code.launchpad.net

Commit message

Introduced Dir/ScopesWatcher classes to watch for updates to the scope install directories, and updated all relevant registry code to propagate an update pub/sub message via middleware.

To post a comment you must log in.
Revision history for this message
Marcus Tomlinson (marcustomlinson) wrote :

I've made changes to the public Registry class so I assume the API version needs to be bumped up. Could someone please give me a run down what exactly I need to do. I've never changed the public API before. Thanks!

Revision history for this message
Marcus Tomlinson (marcustomlinson) wrote :

Although, the change I made doesn't actually break backward compatibility, I've simply added functionality. Do I still need to bump the version number?

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

Yes, the version needs incrementing because you added a new virtual method to a public class. That changes the vtbl layout, so the change is not ABI compatible.

You will need to update the symbols file with the added symbols. Instructions for doing this are in HACKING.

The MICRO version in CMakeLists.txt needs updating.

In addition, debian/changelog needs updating. From the root directory (not the debian directory):

$ dch -v '0.4.8+14.10.20140519-0ubuntu1'

Note the 0.4.8 in the version string, which has to match whatever is in CMakeLists.txt.

Revision history for this message
Michi Henning (michihenning) wrote :

Minor educational rant below :-)

static const int c_buffer_len = 1024 * (c_event_size + 16);

I'm not too keen on the magic numbers here. I had to read the inotify() man page to figure out what's going on.

A better way to size the read buffer might be to call select() to wait until the descriptor becomes ready for reading. Then use ioctl(fd, FIONREAD, &avail) to get the number of available bytes.
Then use avail to size the buffer for reading.

Minor comment:

int length = read(fd_, buffer, c_buffer_len);

This is a little brittle. Personally, I prefer

int length = read(fd_, buffer, sizeof(buffer));

It doesn't matter all that much here but, in situations where there are several buffers involved, this is a little bit more robust. (Note that this will *not* work if the buffer is dynamically allocated and declared as char* or char[].)

Still better:

string s;
s.reserve(avail);
s.size(avail);
int length = read(fd_, &s[0], s.size());

No more magic numbers that way, and it gets extremely unlikely that a future code modification will pass a mismatched buffer and buffer size to read().

Doubleplusgood:

A Buffer class that's instantiated with a file descriptor and buries all the range checking inside its methods. But that would be overkill here, so please don't do that! :-)

    if (fd_ < 0)
    {
        throw ResourceException("DirWatcher(): inotify_init() failed");
    }

The error message isn't great, because it doesn't say why it broke. Throw a unity::SyscallException instead. That exception takes errno and adds it to the output from e.what().

if (length < 0)
{
    throw ResourceException("DirWatcher::watch_thread(): failed to read from inotify fd");
}

Same here: knowing the errno in this case would be nice, so SyscallException is better, and adding the value of fd to the message would be a nice touch.

When this exception is thrown, it doesn't have anywhere to go because the enclosing try-catch swallows it. So, in effect, all the exception does is cause the thread to exit and all the love of the exception is actually lost :-)

Seeing that a read failure is tantamount to an assertion failure (I can't see any reasonable circumstances under which this might fail), it might be easier to just write a message with the fd and errno to stderr and throw 99 to get out of there :-)

The exception handler sets thread_state_ to Failed, but the Failed state is never used elsewhere. Moreover, the next call to remove_watch() will overwrite the Failed state with the Stopping state.

So, as is, the Failed state has no purpose. If there is a Failed state, add_watch() and remove_watch() should probably throw something when called in the Failed state. Of course, what they throw could be an exception that was deposited by the thread into an exception_ptr member, which would allow the exception caught by the watcher thread to eventually make its way back to the caller :-) The destructor could look at the state and print the exception_ptr what() string to cerr. That would deal with the situation where no call to add_watch() or remove_watch() was made after the watcher thread fell over.

Revision history for this message
Michi Henning (michihenning) wrote :

949 + RegistryObject::SPtr registry(new RegistryObject(*signal_handler_wrapper.death_observer, executor, middleware));

Why this change, and the check for a non-null middleware? Can you explain?

review: Needs Information
Revision history for this message
Michi Henning (michihenning) wrote :

When I run the test in a loop, I get a failure within a few dozen iterations:

[ RUN ] Registry.update_notify
/home/michi/src/reg_file_system_monitor/test/gtest/scopes/Registry/Registry_test.cpp:204: Failure
Value of: list.size()
  Actual: 2
Expected: 3

I'm not sure yet whether this is simply because the registry hasn't reacted to the update yet, and the call to list() is happening too early, or because there is something more serious. But it needs looking at.

review: Needs Fixing
Revision history for this message
Michi Henning (michihenning) wrote :

Niggle:

#include "DirWatcher.h"

is redundant in scoperegistry.cpp because ScopesWatcher.h includes that.

I think it would be a nice touch to add some trace to ScopesWatcher that reports when it detects addition, removal, or update of something. This will make it easy for someone to confirm that, when something in the scope config is changed, the registry has indeed reacted to it.

What happens if a scope is *updated* rather than added or removed? I guess this is impossible under normal circumstances? Or is there a way that could happen if a package is updated? If so, we probably need to look for writes to the .ini files and react to them as well as looking for updates to the .so files. (Not sure whether this is really necessary, probably best if we discuss this.)

395. By Marcus Tomlinson

Updated symbols

396. By Marcus Tomlinson

Bumped version number

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
397. By Marcus Tomlinson

Got rid of magic numbers, and improved exception detail in watch_thread.

398. By Marcus Tomlinson

If watch_thread is aborted, rethrow the exception on add/remove_watch(), and print exception on destruction.

399. By Marcus Tomlinson

Remove redundant #include

Revision history for this message
Marcus Tomlinson (marcustomlinson) wrote :

> 949 + RegistryObject::SPtr registry(new
> RegistryObject(*signal_handler_wrapper.death_observer, executor, middleware));
>
> Why this change, and the check for a non-null middleware? Can you explain?

In order for the RegistryObject to create a publisher on the middleware it needs the middleware pointer to do so. I figured the least intrusive change would be to make this parameter optional. If the pointer is not provided, we simply do not register the publisher.

I'm not sure, is this insane? :P

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

> Yes, the version needs incrementing because you added a new virtual method to
> a public class. That changes the vtbl layout, so the change is not ABI
> compatible.

Strictly speaking, as long as the new method is appended to the vtable (which it is), it is ABI compatible, cause users of the lib always get the instance from the lib itself.

But it adds API, so yea, MICRO version should be bumped, plus debian changelog updated.

Revision history for this message
Michi Henning (michihenning) wrote :

> In order for the RegistryObject to create a publisher on the middleware it
> needs the middleware pointer to do so. I figured the least intrusive change
> would be to make this parameter optional. If the pointer is not provided, we
> simply do not register the publisher.
>
> I'm not sure, is this insane? :P

Wouldn't say that it's insane, exactly, but weird :-)

A RegistryObject can't really exist unless there is a middleware for it.
Does the publisher really have to be created by the RegistryObject? If so,
by all means, pass a middleware pointer to the constructor. But that pointer cannot ever be nullptr, as far as I can see.

Or, in scoperegistry.cpp, create the publisher separately and pass the publisher to the RegistryObject. Either one of these might do? I was mainly confused be the optional middleware pointer, which doesn't seem to make sense for a RegistryObject.

BTW, you can always get at the middleware via the RuntimeImpl (mw_base()). Or, inside an operation implementation, you can get at the middleware via the InvokeInfo object.

Revision history for this message
Michi Henning (michihenning) wrote :

> Strictly speaking, as long as the new method is appended to the vtable (which
> it is), it is ABI compatible, cause users of the lib always get the instance
> from the lib itself.

We can't rely on that because the standard doesn't even know about the existence of a vtbl. For all we know, the compiler sorts the vtbl alphabetically by method name… It might happen to work for gcc, if gcc happens to preserve the order in which methods are declared in the vtbl, and the new method is appended to all the others. But that's not something we should rely on.

Revision history for this message
Jussi Pakkanen (jpakkane) wrote :
Revision history for this message
Michi Henning (michihenning) wrote :

> Yes and no:
>
> http://techbase.kde.org/Policies/Binary_Compatibility_Issues_With_C++

Yes, OK, with gcc and clang, we'd actually get away with it.

I'd still prefer to change the version number though. Otherwise, I can write an application that calls the newly-added function and works fine, and then copy the binary for the application to a different machine that has the older version of the library, and the app will fall over when I try to run it because the symbol isn't in the library.

Yes, I know, that's forward compatibility, not backward compatibility. Still, changing the version number is easy and guaranteed to avoid any confusion, so we might as well do it.

Revision history for this message
Jussi Pakkanen (jpakkane) wrote :

You have a trigger here for file creation. That produces a (admittadly very, very, very unlikely) race condition where you start using the file before it is fully written.

If you want to be extra careful, you should use IN_CLOSE_WRITE instead of IN_CREATE for files. I would not say this is a blocker so feel free to do what you deem best.

400. By Marcus Tomlinson

ScopesWatcher now watches for updates in .so files, and updates the registry accordingly.

401. By Marcus Tomlinson

Thread safety

402. By Marcus Tomlinson

Added logging for (un)installation of scopes

403. By Marcus Tomlinson

Updated RegistryObject to expect a middleware pointer on construction, and fixed tests accordingly.

404. By Marcus Tomlinson

Merged devel

405. By Marcus Tomlinson

Fixed changelog

406. By Marcus Tomlinson

Block test until updatesa are received from the registry

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
407. By Marcus Tomlinson

Added -f arguments to system calls in Registry_test

408. By Marcus Tomlinson

Check return values of system() calls, and log verbose output of each command.

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

Michi: Ok, so apart from fixing my dodgey test, I've made all the changes you've suggested. Please could you have another look. Thanks!

Revision history for this message
Michi Henning (michihenning) wrote :

> Michi: Ok, so apart from fixing my dodgey test, I've made all the changes
> you've suggested. Please could you have another look. Thanks!

Will do, thanks!

Jeeesus, what are you doing coding at this time of night? What's the local time where you are? 4:00am?

Revision history for this message
Michi Henning (michihenning) wrote :

219 + throw ResourceException("DirWatcher(): inotify_init() failed");

That one should be SyscallException, I think, so we get the errno.

vector<string> find_scope_dir_configs(string const& scope_dir, string const& suffix)

The implementation of this is virtually identical to the already-existing find_install_dir_configs().

But I changed the implementation of find_install_dir_configs() a day or two ago, to avoid getting bitten by scope.ini files in different directories with the same name: it used to be that the registry got confused if we had, say, fred/fred.ini and joe/fred.ini, because we ended up with the same scope ID twice.

It looks like find_scope_dir_configs() and find_install_dir_configs() should be combined into a single function, so they can't go out of sync. Maybe implement find_install_dir_configs() in terms of find_scope_dir_configs() and move the duplicate check into scope_dir_configs()?

Otherwise, that code is very nicely armor-plated now, thanks!

Just need the test to pass now :-)

review: Needs Fixing
409. By Marcus Tomlinson

Throw SyscallException in DirWatcher constructor to include errno.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
410. By Marcus Tomlinson

Updated find_scope_dir_configs() and find_install_dir_configs() to return maps of name:path.

411. By Marcus Tomlinson

TEST: See if the rm command is causing issues

Revision history for this message
Michi Henning (michihenning) wrote :

Thanks for the fixes to the exception and the code to locate the .ini files!

Just those tests to sort out now. FWIW, I've just pulled and built your r411 and tried it locally. Works fine in a VM on my laptop.

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

Use boost::filesystem rather than system() to manipulate files

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
413. By Marcus Tomlinson

Merged devel and resolved conflicts

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
414. By Marcus Tomlinson

Oops, forgot error_code in filesystem calls

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
415. By Marcus Tomlinson

Upped timeout for wait_for_update

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
416. By Marcus Tomlinson

Replaced nullptr with NULL in select() call

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
417. By Marcus Tomlinson

Merged devel

418. By Marcus Tomlinson

Added some test logging to find where the "Invalid Argument" exception is coming from

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
419. By Marcus Tomlinson

Added a ton of logging

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
420. By Marcus Tomlinson

Added even more logging

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
421. By Marcus Tomlinson

"set_update_callback" renamed to "set_list_update_callback" to better describe the callback behavior

422. By Marcus Tomlinson

Swap tests around

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
423. By Marcus Tomlinson

Create a RegistryObject::SPtr in ScopesWatcher rather than simply referencing it.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
424. By Marcus Tomlinson

Allow only one watch per path in DirWatcher

Revision history for this message
Marcus Tomlinson (marcustomlinson) wrote :

Michi: ok, so now that Jenkins is happy, could you please have another quick scan through this.

There is a lot of noise in commits while I was trying to fix the test failure, but all thats actually changed since the last review is:

* Replaced system() calls with boost::filesystem in the test.
* Allow only one watch per path in DirWatcher.
* Fixed shared_ptr reference issue.

Revision history for this message
Michi Henning (michihenning) wrote :

Well done, champagne due now :-)

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/changelog'
2--- debian/changelog 2014-05-23 07:43:20 +0000
3+++ debian/changelog 2014-05-27 12:46:42 +0000
4@@ -1,3 +1,13 @@
5+unity-scopes-api (0.4.8+14.10.20140519-0ubuntu1) UNRELEASED; urgency=medium
6+
7+ [ Marcus Tomlinson ]
8+ * Introduced Dir/ScopesWatcher classes to watch for updates to the scope install directories.
9+ * Updated scoperegistry to inform the registry object when scopes are (un)installed.
10+ * Updated all relevant registry classes to propagate an update pub/sub message via middleware.
11+ * Added set_update_callback() to Registry (callback executed when the scopes list changes).
12+
13+ -- Marcus Tomlinson <marcustomlinson@ubuntu> Thu, 22 May 2014 06:47:28 +0200
14+
15 unity-scopes-api (0.4.8-0ubuntu1) UNRELEASED; urgency=medium
16
17 [ Pawel Stolowski ]
18
19=== modified file 'debian/libunity-scopes1.symbols'
20--- debian/libunity-scopes1.symbols 2014-05-23 09:32:43 +0000
21+++ debian/libunity-scopes1.symbols 2014-05-27 12:46:42 +0000
22@@ -448,7 +448,7 @@
23 (c++)"unity::scopes::internal::RegistryObject::remove_local_scope(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)@Base" 0.4.0+14.04.20140312.1
24 (c++)"unity::scopes::internal::RegistryObject::set_remote_registry(std::shared_ptr<unity::scopes::internal::MWRegistry> const&)@Base" 0.4.0+14.04.20140312.1
25 (c++)"unity::scopes::internal::RegistryObject::locate(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)@Base" 0.4.0+14.04.20140312.1
26- (c++)"unity::scopes::internal::RegistryObject::RegistryObject(core::posix::ChildProcess::DeathObserver&, std::shared_ptr<unity::scopes::internal::Executor> const&)@Base" 0.4.3+14.10.20140428
27+ (c++)"unity::scopes::internal::RegistryObject::RegistryObject(core::posix::ChildProcess::DeathObserver&, std::shared_ptr<unity::scopes::internal::Executor> const&, std::shared_ptr<unity::scopes::internal::MiddlewareBase>)@Base" 0replaceme
28 (c++)"unity::scopes::internal::RegistryObject::~RegistryObject()@Base" 0.4.0+14.04.20140312.1
29 (c++)"unity::scopes::internal::MiddlewareFactory::MiddlewareData::~MiddlewareData()@Base" 0.4.0+14.04.20140312.1
30 (c++)"unity::scopes::internal::MiddlewareFactory::to_kind(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)@Base" 0.4.0+14.04.20140312.1
31
32=== modified file 'include/unity/scopes/Registry.h'
33--- include/unity/scopes/Registry.h 2014-05-19 08:26:40 +0000
34+++ include/unity/scopes/Registry.h 2014-05-27 12:46:42 +0000
35@@ -74,6 +74,15 @@
36 */
37 virtual MetadataMap list_if(std::function<bool(ScopeMetadata const& item)> predicate) = 0;
38
39+ /**
40+ \brief Assigns a callback method to be executed when the registry's scope list changes.
41+
42+ Note: Upon receiving this callback, you should retrieve the updated scopes list via the list() method if
43+ you wish to retain synchronisation between client and server.
44+ \param callback The function object that is invoked when an update occurs.
45+ */
46+ virtual void set_list_update_callback(std::function<void()> callback) = 0;
47+
48 protected:
49 /// @cond
50 Registry();
51
52=== modified file 'include/unity/scopes/internal/MWRegistry.h'
53--- include/unity/scopes/internal/MWRegistry.h 2014-04-03 12:57:25 +0000
54+++ include/unity/scopes/internal/MWRegistry.h 2014-05-27 12:46:42 +0000
55@@ -20,6 +20,7 @@
56 #define UNITY_SCOPES_INTERNAL_MWREGISTRY_H
57
58 #include <unity/scopes/internal/MWObjectProxy.h>
59+#include <unity/scopes/internal/MWSubscriber.h>
60 #include <unity/scopes/Registry.h>
61 #include <unity/scopes/ScopeMetadata.h>
62
63@@ -42,8 +43,15 @@
64
65 virtual ~MWRegistry();
66
67+ // Local operations
68+ void set_list_update_callback(std::function<void()> callback);
69+
70 protected:
71 MWRegistry(MiddlewareBase* mw_base);
72+
73+private:
74+ MiddlewareBase* mw_base_;
75+ MWSubscriber::UPtr subscriber_;
76 };
77
78 } // namespace internal
79
80=== modified file 'include/unity/scopes/internal/RegistryImpl.h'
81--- include/unity/scopes/internal/RegistryImpl.h 2014-05-19 08:28:08 +0000
82+++ include/unity/scopes/internal/RegistryImpl.h 2014-05-27 12:46:42 +0000
83@@ -43,6 +43,7 @@
84 virtual ScopeMetadata get_metadata(std::string const& scope_id) override;
85 virtual MetadataMap list() override;
86 virtual MetadataMap list_if(std::function<bool(ScopeMetadata const& item)> predicate) override;
87+ virtual void set_list_update_callback(std::function<void()> callback) override;
88
89 // Remote operation. Not part of public API, hence not override.
90 ObjectProxy locate(std::string const& identity);
91
92=== modified file 'include/unity/scopes/internal/RegistryObject.h'
93--- include/unity/scopes/internal/RegistryObject.h 2014-05-07 04:48:26 +0000
94+++ include/unity/scopes/internal/RegistryObject.h 2014-05-27 12:46:42 +0000
95@@ -20,6 +20,8 @@
96 #define UNITY_SCOPES_INTERNAL_REGISTRYOBJECT_H
97
98 #include <unity/scopes/internal/Executor.h>
99+#include <unity/scopes/internal/MiddlewareBase.h>
100+#include <unity/scopes/internal/MWPublisher.h>
101 #include <unity/scopes/internal/MWRegistryProxyFwd.h>
102 #include <unity/scopes/internal/RegistryObjectBase.h>
103 #include <unity/scopes/internal/StateReceiverObject.h>
104@@ -55,7 +57,8 @@
105 public:
106 UNITY_DEFINES_PTRS(RegistryObject);
107
108- RegistryObject(core::posix::ChildProcess::DeathObserver& death_observer, Executor::SPtr const& executor);
109+ RegistryObject(core::posix::ChildProcess::DeathObserver& death_observer, Executor::SPtr const& executor,
110+ MiddlewareBase::SPtr middleware);
111 virtual ~RegistryObject();
112
113 // Remote operation implementations
114@@ -128,6 +131,8 @@
115 ProcessMap scope_processes_;
116 MWRegistryProxy remote_registry_;
117 mutable std::mutex mutex_;
118+
119+ MWPublisher::UPtr publisher_;
120 };
121
122 } // namespace internal
123
124=== modified file 'include/unity/scopes/internal/zmq_middleware/ZmqSubscriber.h'
125--- include/unity/scopes/internal/zmq_middleware/ZmqSubscriber.h 2014-05-19 09:17:14 +0000
126+++ include/unity/scopes/internal/zmq_middleware/ZmqSubscriber.h 2014-05-27 12:46:42 +0000
127@@ -56,7 +56,7 @@
128 NotRunning,
129 Running,
130 Stopping,
131- Failed
132+ Stopped
133 };
134
135 zmqpp::context* const context_;
136
137=== modified file 'include/unity/scopes/testing/MockRegistry.h'
138--- include/unity/scopes/testing/MockRegistry.h 2014-05-19 08:31:25 +0000
139+++ include/unity/scopes/testing/MockRegistry.h 2014-05-27 12:46:42 +0000
140@@ -43,6 +43,7 @@
141 MOCK_METHOD1(get_metadata, ScopeMetadata(std::string const&));
142 MOCK_METHOD0(list, MetadataMap());
143 MOCK_METHOD1(list_if, MetadataMap(std::function<bool(ScopeMetadata const&)>));
144+ MOCK_METHOD1(set_list_update_callback, void(std::function<void()>));
145 };
146
147 /// @endcond
148
149=== modified file 'scoperegistry/CMakeLists.txt'
150--- scoperegistry/CMakeLists.txt 2014-05-19 08:40:28 +0000
151+++ scoperegistry/CMakeLists.txt 2014-05-27 12:46:42 +0000
152@@ -1,6 +1,8 @@
153 set(SRC
154+ DirWatcher.cpp
155 FindFiles.cpp
156 scoperegistry.cpp
157+ ScopesWatcher.cpp
158 )
159
160 include_directories(${CMAKE_CURRENT_SOURCE_DIR})
161
162=== added file 'scoperegistry/DirWatcher.cpp'
163--- scoperegistry/DirWatcher.cpp 1970-01-01 00:00:00 +0000
164+++ scoperegistry/DirWatcher.cpp 2014-05-27 12:46:42 +0000
165@@ -0,0 +1,272 @@
166+/*
167+ * Copyright (C) 2014 Canonical Ltd
168+ *
169+ * This program is free software: you can redistribute it and/or modify
170+ * it under the terms of the GNU Lesser General Public License version 3 as
171+ * published by the Free Software Foundation.
172+ *
173+ * This program is distributed in the hope that it will be useful,
174+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
175+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
176+ * GNU Lesser General Public License for more details.
177+ *
178+ * You should have received a copy of the GNU Lesser General Public License
179+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
180+ *
181+ * Authored by: Marcus Tomlinson <marcus.tomlinson@canonical.com>
182+ */
183+
184+#include "DirWatcher.h"
185+
186+#include <unity/UnityExceptions.h>
187+
188+#include <iostream>
189+#include <sys/inotify.h>
190+#include <sys/ioctl.h>
191+#include <unistd.h>
192+
193+using namespace unity;
194+
195+namespace scoperegistry
196+{
197+
198+DirWatcher::DirWatcher()
199+ : fd_(inotify_init())
200+ , thread_state_(Running)
201+ , thread_exception_(nullptr)
202+{
203+ // Validate the file descriptor
204+ if (fd_ < 0)
205+ {
206+ throw SyscallException("DirWatcher(): inotify_init() failed on inotify fd (fd = " +
207+ std::to_string(fd_) + ")", errno);
208+ }
209+}
210+
211+DirWatcher::~DirWatcher()
212+{
213+ {
214+ std::lock_guard<std::mutex> lock(mutex_);
215+
216+ if (thread_state_ == Failed)
217+ {
218+ try
219+ {
220+ std::rethrow_exception(thread_exception_);
221+ }
222+ catch (std::exception const& e)
223+ {
224+ std::cerr << "~DirWatcher(): " << e.what() << std::endl;
225+ }
226+ catch (...)
227+ {
228+ std::cerr << "~DirWatcher(): watch_thread was aborted due to an unknown exception"
229+ << std::endl;
230+ }
231+ }
232+ else
233+ {
234+ // Set state to Stopping
235+ thread_state_ = Stopping;
236+
237+ // Remove watches (causes read to return)
238+ for (auto& wd : wds_)
239+ {
240+ inotify_rm_watch(fd_, wd.first);
241+ }
242+ wds_.clear();
243+ }
244+ }
245+
246+ // Wait for thread to terminate
247+ if (thread_.joinable())
248+ {
249+ thread_.join();
250+ }
251+
252+ // Close the file descriptor
253+ close(fd_);
254+}
255+
256+void DirWatcher::add_watch(std::string const& path)
257+{
258+ std::lock_guard<std::mutex> lock(mutex_);
259+
260+ if (thread_state_ == Failed)
261+ {
262+ std::rethrow_exception(thread_exception_);
263+ }
264+
265+ for (auto const& wd : wds_)
266+ {
267+ if (wd.second == path)
268+ {
269+ throw ResourceException("DirWatcher::add_watch(): failed to add watch for path: \"" +
270+ path + "\". Watch already exists.");
271+ }
272+ }
273+
274+ int wd = inotify_add_watch(fd_, path.c_str(), IN_CREATE | IN_MOVED_TO |
275+ IN_DELETE | IN_MOVED_FROM |
276+ IN_MODIFY | IN_ATTRIB);
277+ if (wd < 0)
278+ {
279+ throw ResourceException("DirWatcher::add_watch(): failed to add watch for path: \"" +
280+ path + "\". inotify_add_watch() failed. (fd = " +
281+ std::to_string(fd_) + ", path = " + path + ")");
282+ }
283+
284+ wds_[wd] = path;
285+
286+ // If this is the first watch, start the thread
287+ if (wds_.size() == 1)
288+ {
289+ thread_ = std::thread(&DirWatcher::watch_thread, this);
290+ }
291+}
292+
293+void DirWatcher::remove_watch(std::string const& path)
294+{
295+ std::lock_guard<std::mutex> lock(mutex_);
296+
297+ if (thread_state_ == Failed)
298+ {
299+ std::rethrow_exception(thread_exception_);
300+ }
301+
302+ for (auto const& wd : wds_)
303+ {
304+ if (wd.second == path)
305+ {
306+ // If this is the last watch, stop the thread
307+ if (wds_.size() == 1)
308+ {
309+ thread_state_ = Stopping;
310+ }
311+
312+ // Remove watch (causes read to return)
313+ inotify_rm_watch(fd_, wd.first);
314+ wds_.erase(wd.first);
315+ break;
316+ }
317+ }
318+}
319+
320+void DirWatcher::watch_thread()
321+{
322+ try
323+ {
324+ fd_set fds;
325+ FD_ZERO(&fds);
326+ FD_SET(fd_, &fds);
327+
328+ int bytes_avail = 0;
329+ std::string buffer;
330+ std::string event_path;
331+
332+ // Poll for notifications until stop is requested
333+ while (true)
334+ {
335+ // Wait for a payload to arrive
336+ int ret = select(fd_ + 1, &fds, nullptr, nullptr, nullptr);
337+ if (ret < 0)
338+ {
339+ throw SyscallException("DirWatcher::watch_thread(): Thread aborted: "
340+ "select() failed on inotify fd (fd = " +
341+ std::to_string(fd_) + ")", errno);
342+ }
343+ // Get number of bytes available
344+ ret = ioctl(fd_, FIONREAD, &bytes_avail);
345+ if (ret < 0)
346+ {
347+ throw SyscallException("DirWatcher::watch_thread(): Thread aborted: "
348+ "ioctl() failed on inotify fd (fd = " +
349+ std::to_string(fd_) + ")", errno);
350+ }
351+ // Read available bytes
352+ buffer.resize(bytes_avail);
353+ int bytes_read = read(fd_, &buffer[0], buffer.size());
354+ if (bytes_read < 0)
355+ {
356+ throw SyscallException("DirWatcher::watch_thread(): Thread aborted: "
357+ "read() failed on inotify fd (fd = " +
358+ std::to_string(fd_) + ")", errno);
359+ }
360+
361+ // Process event(s) received
362+ int i = 0;
363+ while (i < bytes_read)
364+ {
365+ struct inotify_event* event = (inotify_event*)&buffer[i];
366+ {
367+ event_path = "";
368+ std::lock_guard<std::mutex> lock(mutex_);
369+ if (wds_.find(event->wd) != wds_.end())
370+ {
371+ event_path = wds_.at(event->wd) + "/" + event->name;
372+ }
373+ }
374+
375+ if (event->mask & IN_CREATE || event->mask & IN_MOVED_TO)
376+ {
377+ if (event->mask & IN_ISDIR)
378+ {
379+ watch_event(Added, Directory, event_path);
380+ }
381+ else
382+ {
383+ watch_event(Added, File, event_path);
384+ }
385+ }
386+ else if (event->mask & IN_DELETE || event->mask & IN_MOVED_FROM)
387+ {
388+ if (event->mask & IN_ISDIR)
389+ {
390+ watch_event(Removed, Directory, event_path);
391+ }
392+ else
393+ {
394+ watch_event(Removed, File, event_path);
395+ }
396+ }
397+ else if (event->mask & IN_MODIFY || event->mask & IN_ATTRIB)
398+ {
399+ if (event->mask & IN_ISDIR)
400+ {
401+ watch_event(Modified, Directory, event_path);
402+ }
403+ else
404+ {
405+ watch_event(Modified, File, event_path);
406+ }
407+ }
408+ i += sizeof(inotify_event) + event->len;
409+ }
410+
411+ // Break from the loop if we are stopping
412+ {
413+ std::lock_guard<std::mutex> lock(mutex_);
414+ if (thread_state_ == Stopping)
415+ {
416+ break;
417+ }
418+ }
419+ }
420+ }
421+ catch (std::exception const& e)
422+ {
423+ std::cerr << e.what() << std::endl;
424+ std::lock_guard<std::mutex> lock(mutex_);
425+ thread_state_ = Failed;
426+ thread_exception_ = std::current_exception();
427+ }
428+ catch (...)
429+ {
430+ std::cerr << "DirWatcher::watch_thread(): Thread aborted: unknown exception" << std::endl;
431+ std::lock_guard<std::mutex> lock(mutex_);
432+ thread_state_ = Failed;
433+ thread_exception_ = std::current_exception();
434+ }
435+}
436+
437+} // namespace scoperegistry
438
439=== added file 'scoperegistry/DirWatcher.h'
440--- scoperegistry/DirWatcher.h 1970-01-01 00:00:00 +0000
441+++ scoperegistry/DirWatcher.h 2014-05-27 12:46:42 +0000
442@@ -0,0 +1,79 @@
443+/*
444+ * Copyright (C) 2014 Canonical Ltd
445+ *
446+ * This program is free software: you can redistribute it and/or modify
447+ * it under the terms of the GNU Lesser General Public License version 3 as
448+ * published by the Free Software Foundation.
449+ *
450+ * This program is distributed in the hope that it will be useful,
451+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
452+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
453+ * GNU Lesser General Public License for more details.
454+ *
455+ * You should have received a copy of the GNU Lesser General Public License
456+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
457+ *
458+ * Authored by: Marcus Tomlinson <marcus.tomlinson@canonical.com>
459+ */
460+
461+#ifndef SCOPEREGISTRY_DIRWATCHER_H
462+#define SCOPEREGISTRY_DIRWATCHER_H
463+
464+#include <condition_variable>
465+#include <map>
466+#include <thread>
467+
468+namespace scoperegistry
469+{
470+
471+// DirWatcher watches directories specified by calls to add_watch() / remove_watch() for changes in
472+// the files and folders contained. If a file or folder is added, removed or modified, the pure
473+// virtual watch_event() method is executed (this is to be overridden by a child class deriving
474+// from DirWatcher)
475+
476+class DirWatcher
477+{
478+public:
479+ enum EventType
480+ {
481+ Added,
482+ Removed,
483+ Modified
484+ };
485+
486+ enum FileType
487+ {
488+ File,
489+ Directory
490+ };
491+
492+ DirWatcher();
493+ ~DirWatcher();
494+
495+ void add_watch(std::string const& path);
496+ void remove_watch(std::string const& path);
497+
498+private:
499+ enum ThreadState
500+ {
501+ Running,
502+ Stopping,
503+ Failed
504+ };
505+
506+ int const fd_;
507+
508+ std::map<int, std::string> wds_;
509+
510+ std::thread thread_;
511+ std::mutex mutex_;
512+ ThreadState thread_state_;
513+ std::exception_ptr thread_exception_;
514+
515+ void watch_thread();
516+ virtual void watch_event(EventType event_type, FileType file_type, std::string const& path) = 0;
517+};
518+
519+} // namespace scoperegistry
520+
521+#endif // SCOPEREGISTRY_DIRWATCHER_H
522
523=== modified file 'scoperegistry/FindFiles.cpp'
524--- scoperegistry/FindFiles.cpp 2014-05-21 02:07:16 +0000
525+++ scoperegistry/FindFiles.cpp 2014-05-27 12:46:42 +0000
526@@ -36,14 +36,9 @@
527 namespace scoperegistry
528 {
529
530-namespace
531-{
532-
533 // Return all paths underneath the given dir that are of the given type
534 // or are a symbolic link.
535
536-enum EntryType { File, Directory };
537-
538 vector<string> find_entries(string const& install_dir, EntryType type)
539 {
540 DIR* d = opendir(install_dir.c_str());
541@@ -83,39 +78,56 @@
542 return entries;
543 }
544
545-} // namespace
546+// Return all files of the form dir/<scomescope>.ini that are regular files or
547+// symbolic links and have the specified suffix.
548+// The empty suffix is legal and causes all regular files and symlinks to be returned.
549+
550+map<string, string> find_scope_dir_configs(string const& scope_dir, string const& suffix)
551+{
552+ map<string, string> files;
553+
554+ auto paths = find_entries(scope_dir, File);
555+ for (auto path : paths)
556+ {
557+ filesystem::path fs_path(path);
558+ if (fs_path.extension() != suffix)
559+ {
560+ continue;
561+ }
562+ auto scope_id = fs_path.stem().native();
563+ files.insert(make_pair(scope_id, path));
564+ }
565+
566+ return files;
567+}
568
569 // Return all files of the form dir/*/<scomescope>.ini that are regular files or
570 // symbolic links and have the specified suffix.
571 // The empty suffix is legal and causes all regular files and symlinks to be returned.
572 // Print error message for any scopes with an id that was seen previously.
573
574-vector<string> find_scope_config_files(string const& install_dir,
575- string const& suffix,
576- function<void(string const&)> error)
577+map<string, string> find_install_dir_configs(string const& install_dir,
578+ string const& suffix,
579+ function<void(string const&)> error)
580 {
581- vector<string> files;
582+ map<string, string> files;
583 map<string, string> scopes_seen;
584
585- auto subdirs = find_entries(install_dir, Directory);
586- for (auto subdir : subdirs)
587+ auto scope_dirs = find_entries(install_dir, Directory);
588+ for (auto scope_dir : scope_dirs)
589 {
590- auto candidates = find_entries(subdir, File);
591- for (auto c : candidates)
592+ auto configs = find_scope_dir_configs(scope_dir, suffix);
593+ for (auto config : configs)
594 {
595- filesystem::path path(c);
596- if (path.extension() != suffix) {
597- continue;
598- }
599- auto stem = path.stem().native();
600- auto const it = scopes_seen.find(stem);
601+ auto const it = scopes_seen.find(config.first);
602 if (it != scopes_seen.end())
603 {
604- error("ignoring second instance of non-unique scope: " + path.native() + "\n"
605+ error("ignoring second instance of non-unique scope: " + config.second + "\n"
606 "previous instance: " + it->second);
607+ continue;
608 }
609- scopes_seen[stem] = path.native();
610- files.emplace_back(c);
611+ scopes_seen[config.first] = config.second;
612+ files.insert(config);
613 }
614 }
615
616
617=== modified file 'scoperegistry/FindFiles.h'
618--- scoperegistry/FindFiles.h 2014-05-21 02:07:16 +0000
619+++ scoperegistry/FindFiles.h 2014-05-27 12:46:42 +0000
620@@ -21,12 +21,25 @@
621
622 #include <functional>
623 #include <string>
624+#include <map>
625 #include <vector>
626
627 namespace scoperegistry
628 {
629
630-// Return a vector of file names underneath a scope root install dir that have the given suffix.
631+// Return a vector of all paths found underneath a given dir that are of the given type or are
632+// a symbolic link.
633+
634+enum EntryType { File, Directory };
635+std::vector<std::string> find_entries(std::string const& install_dir, EntryType type);
636+
637+// Return a map of file names:paths underneath a scope dir that have the given suffix. Files are
638+// searched for in the specified directory only, that is, no .ini files in further-nested
639+// directories will be searched.
640+
641+std::map<std::string, std::string> find_scope_dir_configs(std::string const& scope_dir, std::string const& suffix);
642+
643+// Return a map of file names:paths underneath a scope root install dir that have the given suffix.
644 // Files are searched for exactly "one level down", that is, if we have a directory structure.
645 //
646 // canonical/scopeA/myconfig.ini
647@@ -35,14 +48,9 @@
648 // we get those two .ini files, but no .ini files in canonical or underneath
649 // further-nested directories.
650
651-std::vector<std::string> find_scope_config_files(std::string const& install_dir,
652- std::string const& suffix,
653- std::function<void(std::string const&)> error);
654-
655-// Return a vector of file names in dir with the given suffix.
656-
657-std::vector<std::string> find_files(std::string const& dir,
658- std::string const& suffix);
659+std::map<std::string, std::string> find_install_dir_configs(std::string const& install_dir,
660+ std::string const& suffix,
661+ std::function<void(std::string const&)> error);
662
663 } // namespace scoperegistry
664
665
666=== added file 'scoperegistry/ScopesWatcher.cpp'
667--- scoperegistry/ScopesWatcher.cpp 1970-01-01 00:00:00 +0000
668+++ scoperegistry/ScopesWatcher.cpp 2014-05-27 12:46:42 +0000
669@@ -0,0 +1,184 @@
670+/*
671+ * Copyright (C) 2014 Canonical Ltd
672+ *
673+ * This program is free software: you can redistribute it and/or modify
674+ * it under the terms of the GNU Lesser General Public License version 3 as
675+ * published by the Free Software Foundation.
676+ *
677+ * This program is distributed in the hope that it will be useful,
678+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
679+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
680+ * GNU Lesser General Public License for more details.
681+ *
682+ * You should have received a copy of the GNU Lesser General Public License
683+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
684+ *
685+ * Authored by: Marcus Tomlinson <marcus.tomlinson@canonical.com>
686+ */
687+
688+#include "ScopesWatcher.h"
689+
690+#include "FindFiles.h"
691+
692+#include <boost/filesystem/path.hpp>
693+
694+using namespace unity::scopes::internal;
695+using namespace boost;
696+
697+namespace scoperegistry
698+{
699+
700+ScopesWatcher::ScopesWatcher(RegistryObject::SPtr registry,
701+ std::function<void(std::pair<std::string, std::string> const&)> ini_added_callback)
702+ : registry_(registry)
703+ , ini_added_callback_(ini_added_callback)
704+{
705+}
706+
707+void ScopesWatcher::add_install_dir(std::string const& dir)
708+{
709+ try
710+ {
711+ // Add watch for root directory
712+ add_watch(dir);
713+
714+ // Add watches for each sub directory in root
715+ auto subdirs = find_entries(dir, EntryType::Directory);
716+ for (auto const& subdir : subdirs)
717+ {
718+ auto configs = find_scope_dir_configs(subdir, ".ini");
719+ if (!configs.empty())
720+ {
721+ auto config = *configs.cbegin();
722+ std::lock_guard<std::mutex> lock(mutex_);
723+ dir_to_ini_map_[subdir] = config.second;
724+ }
725+ add_watch(subdir);
726+ }
727+ }
728+ catch (...) {}
729+}
730+
731+void ScopesWatcher::add_scope_dir(std::string const& dir)
732+{
733+ auto configs = find_scope_dir_configs(dir, ".ini");
734+ if (!configs.empty())
735+ {
736+ auto config = *configs.cbegin();
737+ // Associate this directory with the contained config file
738+ {
739+ std::lock_guard<std::mutex> lock(mutex_);
740+ dir_to_ini_map_[dir] = config.second;
741+ }
742+
743+ // New config found, execute callback
744+ ini_added_callback_(config);
745+ std::cout << "ScopesWatcher: scope: \"" << config.first << "\" installed to: \""
746+ << dir << "\"" << std::endl;
747+ }
748+
749+ // Add a watch for this directory
750+ add_watch(dir);
751+}
752+
753+void ScopesWatcher::remove_scope_dir(std::string const& dir)
754+{
755+ std::lock_guard<std::mutex> lock(mutex_);
756+
757+ // Check if this directory is associate with the config file
758+ if (dir_to_ini_map_.find(dir) != dir_to_ini_map_.end())
759+ {
760+ // Inform the registry that this scope has been removed
761+ std::string ini_path = dir_to_ini_map_.at(dir);
762+ dir_to_ini_map_.erase(dir);
763+
764+ filesystem::path p(ini_path);
765+ std::string scope_id = p.stem().native();
766+ registry_->remove_local_scope(scope_id);
767+ std::cout << "ScopesWatcher: scope: \"" << scope_id << "\" uninstalled from: \""
768+ << dir << "\"" << std::endl;
769+ }
770+
771+ // Remove the watch for this directory
772+ remove_watch(dir);
773+}
774+
775+void ScopesWatcher::watch_event(DirWatcher::EventType event_type,
776+ DirWatcher::FileType file_type,
777+ std::string const& path)
778+{
779+ filesystem::path fs_path(path);
780+
781+ if (file_type == DirWatcher::File && fs_path.extension() == ".ini")
782+ {
783+ std::lock_guard<std::mutex> lock(mutex_);
784+ std::string parent_path = fs_path.parent_path().native();
785+ std::string scope_id = fs_path.stem().native();
786+
787+ // A .ini has been added / modified
788+ if (event_type == DirWatcher::Added || event_type == DirWatcher::Modified)
789+ {
790+ dir_to_ini_map_[parent_path] = path;
791+ ini_added_callback_(std::make_pair(scope_id, path));
792+ std::cout << "ScopesWatcher: scope: \"" << scope_id << "\" installed to: \""
793+ << parent_path << "\"" << std::endl;
794+ }
795+ // A .ini has been removed
796+ else if (event_type == DirWatcher::Removed)
797+ {
798+ dir_to_ini_map_.erase(parent_path);
799+ registry_->remove_local_scope(scope_id);
800+ std::cout << "ScopesWatcher: scope: \"" << scope_id << "\" uninstalled from: \""
801+ << parent_path << "\"" << std::endl;
802+ }
803+ }
804+ else if (file_type == DirWatcher::File && fs_path.extension() == ".so")
805+ {
806+ std::lock_guard<std::mutex> lock(mutex_);
807+ std::string parent_path = fs_path.parent_path().native();
808+
809+ // Check if this directory is associate with the config file
810+ if (dir_to_ini_map_.find(parent_path) != dir_to_ini_map_.end())
811+ {
812+ std::string ini_path = dir_to_ini_map_.at(parent_path);
813+ filesystem::path fs_ini_path(ini_path);
814+ std::string scope_id = fs_ini_path.stem().native();
815+
816+ // A .so file has been added / modified
817+ if (event_type == DirWatcher::Added || event_type == DirWatcher::Modified)
818+ {
819+ ini_added_callback_(std::make_pair(scope_id, ini_path));
820+ std::cout << "ScopesWatcher: scope: \"" << scope_id << "\" installed to: \""
821+ << parent_path << "\"" << std::endl;
822+ }
823+ // A .so file has been removed
824+ else if (event_type == DirWatcher::Removed)
825+ {
826+ registry_->remove_local_scope(scope_id);
827+ std::cout << "ScopesWatcher: scope: \"" << scope_id << "\" uninstalled from: \""
828+ << parent_path << "\"" << std::endl;
829+ }
830+ }
831+ }
832+ else
833+ {
834+ // A new sub directory has been added
835+ if (event_type == DirWatcher::Added)
836+ {
837+ // try add this path as a scope folder
838+ // (we need to do this with both files and folders added, as the file added may be a symlink)
839+ try
840+ {
841+ add_scope_dir(path);
842+ }
843+ catch (...) {}
844+ }
845+ // A sub directory has been removed
846+ else if (event_type == DirWatcher::Removed)
847+ {
848+ remove_scope_dir(path);
849+ }
850+ }
851+}
852+
853+} // namespace scoperegistry
854
855=== added file 'scoperegistry/ScopesWatcher.h'
856--- scoperegistry/ScopesWatcher.h 1970-01-01 00:00:00 +0000
857+++ scoperegistry/ScopesWatcher.h 2014-05-27 12:46:42 +0000
858@@ -0,0 +1,57 @@
859+/*
860+ * Copyright (C) 2014 Canonical Ltd
861+ *
862+ * This program is free software: you can redistribute it and/or modify
863+ * it under the terms of the GNU Lesser General Public License version 3 as
864+ * published by the Free Software Foundation.
865+ *
866+ * This program is distributed in the hope that it will be useful,
867+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
868+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
869+ * GNU Lesser General Public License for more details.
870+ *
871+ * You should have received a copy of the GNU Lesser General Public License
872+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
873+ *
874+ * Authored by: Marcus Tomlinson <marcus.tomlinson@canonical.com>
875+ */
876+
877+#ifndef SCOPEREGISTRY_SCOPESWATCHER_H
878+#define SCOPEREGISTRY_SCOPESWATCHER_H
879+
880+#include <DirWatcher.h>
881+
882+#include <unity/scopes/internal/RegistryObject.h>
883+
884+namespace scoperegistry
885+{
886+
887+// ScopesWatcher watches the scope install directories specified by calls to add_install_dir() for
888+// the installation / uninstallation of scopes. If a scope is removed, the registry is informed
889+// accordingly. If a scope is added, a user callback (provided on construction) is executed.
890+
891+class ScopesWatcher : public DirWatcher
892+{
893+public:
894+ ScopesWatcher(unity::scopes::internal::RegistryObject::SPtr registry,
895+ std::function<void(std::pair<std::string, std::string> const&)> ini_added_callback);
896+
897+ void add_install_dir(std::string const& dir);
898+
899+private:
900+ unity::scopes::internal::RegistryObject::SPtr const registry_;
901+ std::function<void(std::pair<std::string, std::string> const&)> const ini_added_callback_;
902+ std::map<std::string, std::string> dir_to_ini_map_;
903+ std::mutex mutex_;
904+
905+ void add_scope_dir(std::string const& dir);
906+ void remove_scope_dir(std::string const& dir);
907+
908+ void watch_event(DirWatcher::EventType event_type,
909+ DirWatcher::FileType file_type,
910+ std::string const& path) override;
911+};
912+
913+} // namespace scoperegistry
914+
915+#endif // SCOPEREGISTRY_SCOPESWATCHER_H
916
917=== modified file 'scoperegistry/scoperegistry.cpp'
918--- scoperegistry/scoperegistry.cpp 2014-05-21 02:07:16 +0000
919+++ scoperegistry/scoperegistry.cpp 2014-05-27 12:46:42 +0000
920@@ -17,6 +17,7 @@
921 */
922
923 #include "FindFiles.h"
924+#include "ScopesWatcher.h"
925
926 #include <unity/scopes/internal/MiddlewareFactory.h>
927 #include <unity/scopes/internal/MWRegistry.h>
928@@ -136,26 +137,24 @@
929 map<string, string> fixed_scopes; // Scopes that the OEM cannot override
930 map<string, string> overrideable_scopes; // Scopes that the OEM can override
931
932- auto config_files = find_scope_config_files(scope_installdir, ".ini", error);
933+ auto config_files = find_install_dir_configs(scope_installdir, ".ini", error);
934 for (auto&& path : config_files)
935 {
936- filesystem::path p(path);
937- string scope_id = p.stem().native();
938 try
939 {
940- ScopeConfig config(path);
941+ ScopeConfig config(path.second);
942 if (config.overrideable())
943 {
944- overrideable_scopes[scope_id] = path;
945+ overrideable_scopes[path.first] = path.second;
946 }
947 else
948 {
949- fixed_scopes[scope_id] = path;
950+ fixed_scopes[path.first] = path.second;
951 }
952 }
953 catch (unity::Exception const& e)
954 {
955- error("ignoring scope \"" + scope_id + "\": configuration error:\n" + e.what());
956+ error("ignoring scope \"" + path.first + "\": configuration error:\n" + e.what());
957 }
958 }
959
960@@ -163,19 +162,16 @@
961 {
962 try
963 {
964- auto oem_paths = find_scope_config_files(oem_installdir, ".ini", error);
965+ auto oem_paths = find_install_dir_configs(oem_installdir, ".ini", error);
966 for (auto&& path : oem_paths)
967 {
968- filesystem::path p(path);
969- string file_name = p.filename().native();
970- string scope_id = p.stem().native();
971- if (fixed_scopes.find(scope_id) == fixed_scopes.end())
972+ if (fixed_scopes.find(path.first) == fixed_scopes.end())
973 {
974- overrideable_scopes[scope_id] = path; // Replaces scope if it was present already
975+ overrideable_scopes[path.first] = path.second; // Replaces scope if it was present already
976 }
977 else
978 {
979- error("ignoring non-overrideable scope config \"" + file_name + "\" in OEM directory " + oem_installdir);
980+ error("ignoring non-overrideable scope config \"" + path.second + "\" in OEM directory " + oem_installdir);
981 }
982 }
983 }
984@@ -199,19 +195,16 @@
985 {
986 try
987 {
988- auto click_paths = find_scope_config_files(click_installdir, ".ini", error);
989+ auto click_paths = find_install_dir_configs(click_installdir, ".ini", error);
990 for (auto&& path : click_paths)
991 {
992- filesystem::path p(path);
993- string file_name = p.filename().native();
994- string scope_id = p.stem().native();
995- if (local_scopes.find(scope_id) == local_scopes.end())
996+ if (local_scopes.find(path.first) == local_scopes.end())
997 {
998- click_scopes[scope_id] = path;
999+ click_scopes[path.first] = path.second;
1000 }
1001 else
1002 {
1003- error("ignoring non-overrideable scope config \"" + file_name + "\" in click directory " + click_installdir);
1004+ error("ignoring non-overrideable scope config \"" + path.second + "\" in click directory " + click_installdir);
1005 }
1006 }
1007 }
1008@@ -228,6 +221,94 @@
1009 // For each scope, open the config file for the scope, create the metadata info from the config,
1010 // and add an entry to the RegistryObject.
1011
1012+void add_local_scope(RegistryObject::SPtr const& registry,
1013+ pair<string, string> const& scope,
1014+ MiddlewareBase::SPtr const& mw,
1015+ string const& scoperunner_path,
1016+ string const& config_file,
1017+ bool click,
1018+ int timeout_ms)
1019+{
1020+ unique_ptr<ScopeMetadataImpl> mi(new ScopeMetadataImpl(mw.get()));
1021+ string scope_config(scope.second);
1022+ ScopeConfig sc(scope_config);
1023+
1024+ filesystem::path scope_path(scope_config);
1025+ filesystem::path scope_dir(scope_path.parent_path());
1026+
1027+ mi->set_scope_id(scope.first);
1028+ mi->set_display_name(sc.display_name());
1029+ mi->set_description(sc.description());
1030+ mi->set_author(sc.author());
1031+ mi->set_invisible(sc.invisible());
1032+ mi->set_appearance_attributes(sc.appearance_attributes());
1033+ mi->set_scope_directory(scope_dir.native());
1034+ mi->set_results_ttl_type(sc.results_ttl_type());
1035+
1036+ try
1037+ {
1038+ mi->set_art(relative_scope_path_to_abs_path(sc.art(), scope_dir).native());
1039+ }
1040+ catch (NotFoundException const&)
1041+ {
1042+ }
1043+ try
1044+ {
1045+ mi->set_icon(relative_scope_path_to_abs_path(sc.icon(), scope_dir).native());
1046+ }
1047+ catch (NotFoundException const&)
1048+ {
1049+ }
1050+ try
1051+ {
1052+ mi->set_search_hint(sc.search_hint());
1053+ }
1054+ catch (NotFoundException const&)
1055+ {
1056+ }
1057+ try
1058+ {
1059+ mi->set_hot_key(sc.hot_key());
1060+ }
1061+ catch (NotFoundException const&)
1062+ {
1063+ }
1064+
1065+ ScopeProxy proxy = ScopeImpl::create(mw->create_scope_proxy(scope.first), mw->runtime(), scope.first);
1066+ mi->set_proxy(proxy);
1067+ auto meta = ScopeMetadataImpl::create(move(mi));
1068+
1069+ RegistryObject::ScopeExecData exec_data;
1070+ exec_data.scope_id = scope.first;
1071+ // get custom scope runner executable, if not set use default scoperunner
1072+ exec_data.scoperunner_path = scoperunner_path;
1073+
1074+ if (click)
1075+ {
1076+ exec_data.confinement_profile =
1077+ scope_path.parent_path().filename().native();
1078+ }
1079+
1080+ exec_data.timeout_ms = timeout_ms;
1081+
1082+ try
1083+ {
1084+ auto custom_exec = sc.scope_runner();
1085+ if (custom_exec.empty())
1086+ {
1087+ throw unity::InvalidArgumentException("Invalid scope runner executable for scope: " + scope.first);
1088+ }
1089+ exec_data.scoperunner_path = relative_scope_path_to_abs_path(custom_exec, scope_dir).native();
1090+ }
1091+ catch (NotFoundException const&)
1092+ {
1093+ }
1094+ exec_data.runtime_config = config_file;
1095+ exec_data.scope_config = scope.second;
1096+
1097+ registry->add_local_scope(scope.first, move(meta), exec_data);
1098+}
1099+
1100 void add_local_scopes(RegistryObject::SPtr const& registry,
1101 map<string, string> const& all_scopes,
1102 MiddlewareBase::SPtr const& mw,
1103@@ -240,84 +321,7 @@
1104 {
1105 try
1106 {
1107- unique_ptr<ScopeMetadataImpl> mi(new ScopeMetadataImpl(mw.get()));
1108- string scope_config(pair.second);
1109- ScopeConfig sc(scope_config);
1110-
1111- filesystem::path scope_path(scope_config);
1112- filesystem::path scope_dir(scope_path.parent_path());
1113-
1114- mi->set_scope_id(pair.first);
1115- mi->set_display_name(sc.display_name());
1116- mi->set_description(sc.description());
1117- mi->set_author(sc.author());
1118- mi->set_invisible(sc.invisible());
1119- mi->set_appearance_attributes(sc.appearance_attributes());
1120- mi->set_scope_directory(scope_dir.native());
1121- mi->set_results_ttl_type(sc.results_ttl_type());
1122-
1123- try
1124- {
1125- mi->set_art(relative_scope_path_to_abs_path(sc.art(), scope_dir).native());
1126- }
1127- catch (NotFoundException const&)
1128- {
1129- }
1130- try
1131- {
1132- mi->set_icon(relative_scope_path_to_abs_path(sc.icon(), scope_dir).native());
1133- }
1134- catch (NotFoundException const&)
1135- {
1136- }
1137- try
1138- {
1139- mi->set_search_hint(sc.search_hint());
1140- }
1141- catch (NotFoundException const&)
1142- {
1143- }
1144- try
1145- {
1146- mi->set_hot_key(sc.hot_key());
1147- }
1148- catch (NotFoundException const&)
1149- {
1150- }
1151-
1152- ScopeProxy proxy = ScopeImpl::create(mw->create_scope_proxy(pair.first), mw->runtime(), pair.first);
1153- mi->set_proxy(proxy);
1154- auto meta = ScopeMetadataImpl::create(move(mi));
1155-
1156- RegistryObject::ScopeExecData exec_data;
1157- exec_data.scope_id = pair.first;
1158- // get custom scope runner executable, if not set use default scoperunner
1159- exec_data.scoperunner_path = scoperunner_path;
1160-
1161- if (click)
1162- {
1163- exec_data.confinement_profile =
1164- scope_path.parent_path().filename().native();
1165- }
1166-
1167- exec_data.timeout_ms = timeout_ms;
1168-
1169- try
1170- {
1171- auto custom_exec = sc.scope_runner();
1172- if (custom_exec.empty())
1173- {
1174- throw unity::InvalidArgumentException("Invalid scope runner executable for scope: " + pair.first);
1175- }
1176- exec_data.scoperunner_path = relative_scope_path_to_abs_path(custom_exec, scope_dir).native();
1177- }
1178- catch (NotFoundException const&)
1179- {
1180- }
1181- exec_data.runtime_config = config_file;
1182- exec_data.scope_config = pair.second;
1183-
1184- registry->add_local_scope(pair.first, move(meta), exec_data);
1185+ add_local_scope(registry, pair, mw, scoperunner_path, config_file, click, timeout_ms);
1186 }
1187 catch (unity::Exception const& e)
1188 {
1189@@ -403,7 +407,7 @@
1190
1191 // The registry object stores the local and remote scopes
1192 Executor::SPtr executor = make_shared<Executor>();
1193- RegistryObject::SPtr registry(new RegistryObject(*signal_handler_wrapper.death_observer, executor));
1194+ RegistryObject::SPtr registry(new RegistryObject(*signal_handler_wrapper.death_observer, executor, middleware));
1195
1196 // Add the metadata for each scope to the lookup table.
1197 // We do this before starting any of the scopes, so aggregating scopes don't get a lookup failure if
1198@@ -433,6 +437,38 @@
1199 registry->set_remote_registry(middleware->ss_registry_proxy());
1200 }
1201
1202+ // Configure watches for scope install directories
1203+ ScopesWatcher local_scopes_watcher(registry,
1204+ [registry, &middleware, &scoperunner_path, &config_file, process_timeout]
1205+ (pair<string, string> const& scope)
1206+ {
1207+ try
1208+ {
1209+ add_local_scope(registry, scope, middleware, scoperunner_path, config_file, false, process_timeout);
1210+ }
1211+ catch (unity::Exception const& e)
1212+ {
1213+ error("ignoring installed scope \"" + scope.first + "\": cannot create metadata: " + e.what());
1214+ }
1215+ });
1216+ local_scopes_watcher.add_install_dir(scope_installdir);
1217+ local_scopes_watcher.add_install_dir(oem_installdir);
1218+
1219+ ScopesWatcher click_scopes_watcher(registry,
1220+ [registry, &middleware, &scoperunner_path, &config_file, process_timeout]
1221+ (pair<string, string> const& scope)
1222+ {
1223+ try
1224+ {
1225+ add_local_scope(registry, scope, middleware, scoperunner_path, config_file, true, process_timeout);
1226+ }
1227+ catch (unity::Exception const& e)
1228+ {
1229+ error("ignoring installed scope \"" + scope.first + "\": cannot create metadata: " + e.what());
1230+ }
1231+ });
1232+ click_scopes_watcher.add_install_dir(click_installdir);
1233+
1234 // Let's add the registry's state receiver to the middleware so that scopes can inform
1235 // the registry of state changes.
1236 middleware->add_state_receiver_object("StateReceiver", registry->state_receiver());
1237
1238=== modified file 'src/scopes/internal/MWRegistry.cpp'
1239--- src/scopes/internal/MWRegistry.cpp 2014-01-23 11:28:34 +0000
1240+++ src/scopes/internal/MWRegistry.cpp 2014-05-27 12:46:42 +0000
1241@@ -16,7 +16,9 @@
1242 * Authored by: Michi Henning <michi.henning@canonical.com>
1243 */
1244
1245+#include <unity/scopes/internal/MiddlewareBase.h>
1246 #include <unity/scopes/internal/MWRegistry.h>
1247+#include <unity/scopes/internal/RuntimeImpl.h>
1248
1249 using namespace std;
1250
1251@@ -30,7 +32,8 @@
1252 {
1253
1254 MWRegistry::MWRegistry(MiddlewareBase* mw_base) :
1255- MWObjectProxy(mw_base)
1256+ MWObjectProxy(mw_base),
1257+ mw_base_(mw_base)
1258 {
1259 }
1260
1261@@ -38,6 +41,26 @@
1262 {
1263 }
1264
1265+void MWRegistry::set_list_update_callback(std::function<void()> callback)
1266+{
1267+ if (!subscriber_)
1268+ {
1269+ // Use lazy initialization here to only subscribe to the publisher if a callback is set
1270+ try
1271+ {
1272+ subscriber_ = mw_base_->create_subscriber(mw_base_->runtime()->registry_identity());
1273+ }
1274+ catch (std::exception const& e)
1275+ {
1276+ cerr << "MWRegistry::set_list_update_callback(): failed to create registry subscriber: " << e.what() << endl;
1277+ }
1278+ }
1279+ if (subscriber_)
1280+ {
1281+ subscriber_->set_message_callback([callback](string const&){ callback(); });
1282+ }
1283+}
1284+
1285 } // namespace internal
1286
1287 } // namespace scopes
1288
1289=== modified file 'src/scopes/internal/RegistryImpl.cpp'
1290--- src/scopes/internal/RegistryImpl.cpp 2014-05-19 08:28:08 +0000
1291+++ src/scopes/internal/RegistryImpl.cpp 2014-05-27 12:46:42 +0000
1292@@ -71,6 +71,11 @@
1293 return matching_entries;
1294 }
1295
1296+void RegistryImpl::set_list_update_callback(std::function<void()> callback)
1297+{
1298+ fwd()->set_list_update_callback(callback);
1299+}
1300+
1301 MWRegistryProxy RegistryImpl::fwd()
1302 {
1303 return dynamic_pointer_cast<MWRegistry>(proxy());
1304
1305=== modified file 'src/scopes/internal/RegistryObject.cpp'
1306--- src/scopes/internal/RegistryObject.cpp 2014-05-23 07:43:20 +0000
1307+++ src/scopes/internal/RegistryObject.cpp 2014-05-27 12:46:42 +0000
1308@@ -19,6 +19,7 @@
1309 #include <unity/scopes/internal/RegistryObject.h>
1310
1311 #include <unity/scopes/internal/MWRegistry.h>
1312+#include <unity/scopes/internal/RuntimeImpl.h>
1313 #include <unity/scopes/ScopeExceptions.h>
1314 #include <unity/UnityExceptions.h>
1315
1316@@ -38,7 +39,8 @@
1317 namespace internal
1318 {
1319
1320-RegistryObject::RegistryObject(core::posix::ChildProcess::DeathObserver& death_observer, Executor::SPtr const& executor)
1321+RegistryObject::RegistryObject(core::posix::ChildProcess::DeathObserver& death_observer, Executor::SPtr const& executor,
1322+ MiddlewareBase::SPtr middleware)
1323 : death_observer_(death_observer),
1324 death_observer_connection_
1325 {
1326@@ -58,6 +60,17 @@
1327 },
1328 executor_(executor)
1329 {
1330+ if (middleware)
1331+ {
1332+ try
1333+ {
1334+ publisher_ = middleware->create_publisher(middleware->runtime()->registry_identity());
1335+ }
1336+ catch (std::exception const& e)
1337+ {
1338+ std::cerr << "RegistryObject(): failed to create registry publisher: " << e.what() << endl;
1339+ }
1340+ }
1341 }
1342
1343 RegistryObject::~RegistryObject()
1344@@ -209,6 +222,12 @@
1345 }
1346 scopes_.insert(make_pair(scope_id, metadata));
1347 scope_processes_.insert(make_pair(scope_id, ScopeProcess(exec_data)));
1348+
1349+ if (publisher_)
1350+ {
1351+ // Send a blank message to subscribers to inform them that the registry has been updated
1352+ publisher_->send_message("");
1353+ }
1354 return return_value;
1355 }
1356
1357@@ -224,7 +243,18 @@
1358 lock_guard<decltype(mutex_)> lock(mutex_);
1359
1360 scope_processes_.erase(scope_id);
1361- return scopes_.erase(scope_id) == 1;
1362+
1363+ if (scopes_.erase(scope_id) == 1)
1364+ {
1365+ if (publisher_)
1366+ {
1367+ // Send a blank message to subscribers to inform them that the registry has been updated
1368+ publisher_->send_message("");
1369+ }
1370+ return true;
1371+ }
1372+
1373+ return false;
1374 }
1375
1376 void RegistryObject::set_remote_registry(MWRegistryProxy const& remote_registry)
1377
1378=== modified file 'src/scopes/internal/zmq_middleware/ZmqSubscriber.cpp'
1379--- src/scopes/internal/zmq_middleware/ZmqSubscriber.cpp 2014-05-19 09:17:14 +0000
1380+++ src/scopes/internal/zmq_middleware/ZmqSubscriber.cpp 2014-05-27 12:46:42 +0000
1381@@ -67,9 +67,9 @@
1382 thread_ = std::thread(&ZmqSubscriber::subscriber_thread, this);
1383
1384 std::unique_lock<std::mutex> lock(mutex_);
1385- cond_.wait(lock, [this] { return thread_state_ == Running || thread_state_ == Failed; });
1386+ cond_.wait(lock, [this] { return thread_state_ == Running || thread_state_ == Stopped; });
1387
1388- if (thread_state_ == Failed)
1389+ if (thread_state_ == Stopped)
1390 {
1391 if (thread_.joinable())
1392 {
1393@@ -89,7 +89,11 @@
1394
1395 ZmqSubscriber::~ZmqSubscriber()
1396 {
1397- thread_stopper_->stop();
1398+ {
1399+ std::lock_guard<std::mutex> lock(mutex_);
1400+ callback_ = nullptr;
1401+ thread_stopper_ = nullptr;
1402+ }
1403
1404 if (thread_.joinable())
1405 {
1406@@ -137,6 +141,7 @@
1407 std::string message;
1408 while (true)
1409 {
1410+ // poll() throws when the zmq context is destroyed (hense stopping the thread)
1411 poller.poll();
1412
1413 // Flush out the message queue before stopping the thread
1414@@ -170,8 +175,10 @@
1415 catch (...)
1416 {
1417 std::lock_guard<std::mutex> lock(mutex_);
1418+ callback_ = nullptr;
1419+ thread_stopper_ = nullptr;
1420 thread_exception_ = std::current_exception();
1421- thread_state_ = Failed;
1422+ thread_state_ = Stopped;
1423 cond_.notify_all();
1424 }
1425 }
1426
1427=== modified file 'test/gtest/scopes/Registry/CMakeLists.txt'
1428--- test/gtest/scopes/Registry/CMakeLists.txt 2014-04-03 16:50:44 +0000
1429+++ test/gtest/scopes/Registry/CMakeLists.txt 2014-05-27 12:46:42 +0000
1430@@ -13,3 +13,4 @@
1431
1432 add_test(Registry Registry_test)
1433 add_subdirectory(scopes)
1434+add_subdirectory(other_scopes)
1435
1436=== modified file 'test/gtest/scopes/Registry/Registry_test.cpp'
1437--- test/gtest/scopes/Registry/Registry_test.cpp 2014-05-23 00:58:11 +0000
1438+++ test/gtest/scopes/Registry/Registry_test.cpp 2014-05-27 12:46:42 +0000
1439@@ -23,14 +23,15 @@
1440 #include <unity/scopes/CategorisedResult.h>
1441 #include <gtest/gtest.h>
1442
1443+#include <boost/filesystem/operations.hpp>
1444 #include <condition_variable>
1445 #include <functional>
1446 #include <mutex>
1447+#include <signal.h>
1448 #include <thread>
1449-
1450-#include <signal.h>
1451 #include <unistd.h>
1452
1453+using namespace boost;
1454 using namespace unity::scopes;
1455
1456 class Receiver : public SearchListenerBase
1457@@ -114,6 +115,141 @@
1458 EXPECT_TRUE(receiver->wait_until_finished());
1459 }
1460
1461+TEST(Registry, update_notify)
1462+{
1463+ bool update_received_ = false;
1464+ std::mutex mutex_;
1465+ std::condition_variable cond_;
1466+
1467+ Runtime::UPtr rt = Runtime::create(TEST_RUNTIME_FILE);
1468+ RegistryProxy r = rt->registry();
1469+
1470+ // Configure registry update callback
1471+ r->set_list_update_callback([&update_received_, &mutex_, &cond_]
1472+ {
1473+ std::lock_guard<std::mutex> lock(mutex_);
1474+ update_received_ = true;
1475+ cond_.notify_one();
1476+ });
1477+ auto wait_for_update = [&update_received_, &mutex_, &cond_]
1478+ {
1479+ // Flush out update notifications
1480+ std::unique_lock<std::mutex> lock(mutex_);
1481+ while (cond_.wait_for(lock, std::chrono::milliseconds(500), [&update_received_] { return update_received_; }))
1482+ {
1483+ update_received_ = false;
1484+ }
1485+ update_received_ = false;
1486+ };
1487+
1488+ system::error_code ec;
1489+
1490+ // First check that we have 2 scopes registered
1491+ MetadataMap list = r->list();
1492+ EXPECT_EQ(2, list.size());
1493+ EXPECT_NE(list.end(), list.find("testscopeA"));
1494+ EXPECT_NE(list.end(), list.find("testscopeB"));
1495+ EXPECT_EQ(list.end(), list.find("testscopeC"));
1496+ EXPECT_EQ(list.end(), list.find("testscopeD"));
1497+
1498+ // Move testscopeC into the scopes folder
1499+ std::cout << "Move testscopeC into the scopes folder" << std::endl;
1500+ filesystem::rename(TEST_RUNTIME_PATH "/other_scopes/testscopeC", TEST_RUNTIME_PATH "/scopes/testscopeC", ec);
1501+ ASSERT_EQ("Success", ec.message());
1502+ wait_for_update();
1503+
1504+ // Now check that we have 3 scopes registered
1505+ list = r->list();
1506+ EXPECT_EQ(3, list.size());
1507+ EXPECT_NE(list.end(), list.find("testscopeA"));
1508+ EXPECT_NE(list.end(), list.find("testscopeB"));
1509+ EXPECT_NE(list.end(), list.find("testscopeC"));
1510+ EXPECT_EQ(list.end(), list.find("testscopeD"));
1511+
1512+ // Make a symlink to testscopeD in the scopes folder
1513+ std::cout << "Make a symlink to testscopeD in the scopes folder" << std::endl;
1514+ filesystem::create_symlink(TEST_RUNTIME_PATH "/other_scopes/testscopeD", TEST_RUNTIME_PATH "/scopes/testscopeD", ec);
1515+ ASSERT_EQ("Success", ec.message());
1516+ wait_for_update();
1517+
1518+ // Now check that we have 4 scopes registered
1519+ list = r->list();
1520+ EXPECT_EQ(4, list.size());
1521+ EXPECT_NE(list.end(), list.find("testscopeA"));
1522+ EXPECT_NE(list.end(), list.find("testscopeB"));
1523+ EXPECT_NE(list.end(), list.find("testscopeC"));
1524+ EXPECT_NE(list.end(), list.find("testscopeD"));
1525+
1526+ // Move testscopeC back into the other_scopes folder
1527+ std::cout << "Move testscopeC back into the other_scopes folder" << std::endl;
1528+ filesystem::rename(TEST_RUNTIME_PATH "/scopes/testscopeC", TEST_RUNTIME_PATH "/other_scopes/testscopeC", ec);
1529+ ASSERT_EQ("Success", ec.message());
1530+ wait_for_update();
1531+
1532+ // Now check that we have 3 scopes registered again
1533+ list = r->list();
1534+ EXPECT_EQ(3, list.size());
1535+ EXPECT_NE(list.end(), list.find("testscopeA"));
1536+ EXPECT_NE(list.end(), list.find("testscopeB"));
1537+ EXPECT_EQ(list.end(), list.find("testscopeC"));
1538+ EXPECT_NE(list.end(), list.find("testscopeD"));
1539+
1540+ // Remove symlink to testscopeD from the scopes folder
1541+ std::cout << "Remove symlink to testscopeD from the scopes folder" << std::endl;
1542+ filesystem::remove(TEST_RUNTIME_PATH "/scopes/testscopeD", ec);
1543+ ASSERT_EQ("Success", ec.message());
1544+ wait_for_update();
1545+
1546+ // Now check that we are back to having 2 scopes registered
1547+ list = r->list();
1548+ EXPECT_EQ(2, list.size());
1549+ EXPECT_NE(list.end(), list.find("testscopeA"));
1550+ EXPECT_NE(list.end(), list.find("testscopeB"));
1551+ EXPECT_EQ(list.end(), list.find("testscopeC"));
1552+ EXPECT_EQ(list.end(), list.find("testscopeD"));
1553+
1554+ // Make a folder in scopes named "testfolder"
1555+ std::cout << "Make a folder in scopes named \"testfolder\"" << std::endl;
1556+ filesystem::create_directory(TEST_RUNTIME_PATH "/scopes/testfolder", ec);
1557+ ASSERT_EQ("Success", ec.message());
1558+
1559+ // Check that no scopes were registered
1560+ list = r->list();
1561+ EXPECT_EQ(2, list.size());
1562+ EXPECT_NE(list.end(), list.find("testscopeA"));
1563+ EXPECT_NE(list.end(), list.find("testscopeB"));
1564+ EXPECT_EQ(list.end(), list.find("testscopeC"));
1565+ EXPECT_EQ(list.end(), list.find("testscopeD"));
1566+
1567+ // Make a symlink to testscopeC.ini in testfolder
1568+ std::cout << "Make a symlink to testscopeC.ini in testfolder" << std::endl;
1569+ filesystem::create_symlink(TEST_RUNTIME_PATH "/other_scopes/testscopeC/testscopeC.ini", TEST_RUNTIME_PATH "/scopes/testfolder/testscopeC.ini", ec);
1570+ ASSERT_EQ("Success", ec.message());
1571+ wait_for_update();
1572+
1573+ // Now check that we have 3 scopes registered
1574+ list = r->list();
1575+ EXPECT_EQ(3, list.size());
1576+ EXPECT_NE(list.end(), list.find("testscopeA"));
1577+ EXPECT_NE(list.end(), list.find("testscopeB"));
1578+ EXPECT_NE(list.end(), list.find("testscopeC"));
1579+ EXPECT_EQ(list.end(), list.find("testscopeD"));
1580+
1581+ // Remove testfolder
1582+ std::cout << "Remove testfolder" << std::endl;
1583+ filesystem::remove_all(TEST_RUNTIME_PATH "/scopes/testfolder", ec);
1584+ ASSERT_EQ("Success", ec.message());
1585+ wait_for_update();
1586+
1587+ // Now check that we are back to having 2 scopes registered
1588+ list = r->list();
1589+ EXPECT_EQ(2, list.size());
1590+ EXPECT_NE(list.end(), list.find("testscopeA"));
1591+ EXPECT_NE(list.end(), list.find("testscopeB"));
1592+ EXPECT_EQ(list.end(), list.find("testscopeC"));
1593+ EXPECT_EQ(list.end(), list.find("testscopeD"));
1594+}
1595+
1596 int main(int argc, char **argv)
1597 {
1598 ::testing::InitGoogleTest(&argc, argv);
1599
1600=== added directory 'test/gtest/scopes/Registry/other_scopes'
1601=== added file 'test/gtest/scopes/Registry/other_scopes/CMakeLists.txt'
1602--- test/gtest/scopes/Registry/other_scopes/CMakeLists.txt 1970-01-01 00:00:00 +0000
1603+++ test/gtest/scopes/Registry/other_scopes/CMakeLists.txt 2014-05-27 12:46:42 +0000
1604@@ -0,0 +1,2 @@
1605+add_subdirectory(testscopeC)
1606+add_subdirectory(testscopeD)
1607
1608=== added directory 'test/gtest/scopes/Registry/other_scopes/testscopeC'
1609=== added file 'test/gtest/scopes/Registry/other_scopes/testscopeC/CMakeLists.txt'
1610--- test/gtest/scopes/Registry/other_scopes/testscopeC/CMakeLists.txt 1970-01-01 00:00:00 +0000
1611+++ test/gtest/scopes/Registry/other_scopes/testscopeC/CMakeLists.txt 2014-05-27 12:46:42 +0000
1612@@ -0,0 +1,1 @@
1613+configure_file(testscopeC.ini.in testscopeC.ini)
1614
1615=== added file 'test/gtest/scopes/Registry/other_scopes/testscopeC/testscopeC.ini.in'
1616--- test/gtest/scopes/Registry/other_scopes/testscopeC/testscopeC.ini.in 1970-01-01 00:00:00 +0000
1617+++ test/gtest/scopes/Registry/other_scopes/testscopeC/testscopeC.ini.in 2014-05-27 12:46:42 +0000
1618@@ -0,0 +1,8 @@
1619+[ScopeConfig]
1620+DisplayName = scope-C.DisplayName
1621+Description = scope-C.Description
1622+Art = /foo/scope-C.Art
1623+Author = Canonical Ltd.
1624+Icon = /foo/scope-C.Icon
1625+SearchHint = scope-C.SearchHint
1626+HotKey = scope-C.HotKey
1627
1628=== added directory 'test/gtest/scopes/Registry/other_scopes/testscopeD'
1629=== added file 'test/gtest/scopes/Registry/other_scopes/testscopeD/CMakeLists.txt'
1630--- test/gtest/scopes/Registry/other_scopes/testscopeD/CMakeLists.txt 1970-01-01 00:00:00 +0000
1631+++ test/gtest/scopes/Registry/other_scopes/testscopeD/CMakeLists.txt 2014-05-27 12:46:42 +0000
1632@@ -0,0 +1,1 @@
1633+configure_file(testscopeD.ini.in testscopeD.ini)
1634
1635=== added file 'test/gtest/scopes/Registry/other_scopes/testscopeD/testscopeD.ini.in'
1636--- test/gtest/scopes/Registry/other_scopes/testscopeD/testscopeD.ini.in 1970-01-01 00:00:00 +0000
1637+++ test/gtest/scopes/Registry/other_scopes/testscopeD/testscopeD.ini.in 2014-05-27 12:46:42 +0000
1638@@ -0,0 +1,8 @@
1639+[ScopeConfig]
1640+DisplayName = scope-D.DisplayName
1641+Description = scope-D.Description
1642+Art = /foo/scope-D.Art
1643+Author = Canonical Ltd.
1644+Icon = /foo/scope-D.Icon
1645+SearchHint = scope-D.SearchHint
1646+HotKey = scope-D.HotKey
1647
1648=== modified file 'test/gtest/scopes/internal/RegistryObject/RegistryObject_test.cpp'
1649--- test/gtest/scopes/internal/RegistryObject/RegistryObject_test.cpp 2014-05-22 08:45:05 +0000
1650+++ test/gtest/scopes/internal/RegistryObject/RegistryObject_test.cpp 2014-05-27 12:46:42 +0000
1651@@ -164,7 +164,7 @@
1652 exec_data.confinement_profile = confinement_profile;
1653 exec_data.timeout_ms = 1500;
1654
1655- registry.reset(new RegistryObject(*death_observer(), executor));
1656+ registry.reset(new RegistryObject(*death_observer(), executor, nullptr));
1657 registry->add_local_scope("scope-id", meta, exec_data);
1658 registry->locate("scope-id");
1659 EXPECT_TRUE(registry->is_scope_running("scope-id"));
1660
1661=== modified file 'test/gtest/scopes/internal/zmq_middleware/RegistryI/RegistryI_test.cpp'
1662--- test/gtest/scopes/internal/zmq_middleware/RegistryI/RegistryI_test.cpp 2014-05-22 23:21:46 +0000
1663+++ test/gtest/scopes/internal/zmq_middleware/RegistryI/RegistryI_test.cpp 2014-05-27 12:46:42 +0000
1664@@ -100,7 +100,7 @@
1665
1666 MiddlewareBase::SPtr middleware = runtime->factory()->create(identity, mw_kind, mw_configfile);
1667 Executor::SPtr executor = make_shared<Executor>();
1668- RegistryObject::SPtr ro(make_shared<RegistryObject>(*scope.death_observer, executor));
1669+ RegistryObject::SPtr ro(make_shared<RegistryObject>(*scope.death_observer, executor, middleware));
1670 auto registry = middleware->add_registry_object(identity, ro);
1671 auto p = middleware->create_scope_proxy("scope1", "ipc:///tmp/scope1");
1672 EXPECT_TRUE(ro->add_local_scope("scope1", move(make_meta("scope1", p, middleware)),
1673@@ -123,7 +123,7 @@
1674
1675 MiddlewareBase::SPtr middleware = runtime->factory()->create(identity, mw_kind, mw_configfile);
1676 Executor::SPtr executor = make_shared<Executor>();
1677- RegistryObject::SPtr ro(make_shared<RegistryObject>(*scope.death_observer, executor));
1678+ RegistryObject::SPtr ro(make_shared<RegistryObject>(*scope.death_observer, executor, middleware));
1679 auto registry = middleware->add_registry_object(identity, ro);
1680
1681 auto r = runtime->registry();
1682@@ -172,7 +172,7 @@
1683
1684 MiddlewareBase::SPtr middleware = runtime->factory()->create(identity, mw_kind, mw_configfile);
1685 Executor::SPtr executor = make_shared<Executor>();
1686- RegistryObject::SPtr ro(make_shared<RegistryObject>(*scope.death_observer, executor));
1687+ RegistryObject::SPtr ro(make_shared<RegistryObject>(*scope.death_observer, executor, middleware));
1688 auto registry = middleware->add_registry_object(identity, ro);
1689
1690 auto r = runtime->registry();
1691@@ -224,7 +224,7 @@
1692
1693 MiddlewareBase::SPtr middleware = runtime->factory()->create(identity, mw_kind, mw_configfile);
1694 Executor::SPtr executor = make_shared<Executor>();
1695- RegistryObject::SPtr ro(make_shared<RegistryObject>(*scope.death_observer, executor));
1696+ RegistryObject::SPtr ro(make_shared<RegistryObject>(*scope.death_observer, executor, middleware));
1697 RegistryObject::ScopeExecData dummy_exec_data;
1698 auto registry = middleware->add_registry_object(identity, ro);
1699 auto proxy = middleware->create_scope_proxy("scope1", "ipc:///tmp/scope1");
1700@@ -286,7 +286,7 @@
1701 {
1702 public:
1703 MockRegistryObject(core::posix::ChildProcess::DeathObserver& death_observer)
1704- : RegistryObject(death_observer, make_shared<Executor>())
1705+ : RegistryObject(death_observer, make_shared<Executor>(), nullptr)
1706 {
1707 }
1708
1709@@ -393,7 +393,7 @@
1710 mw = rt->factory()->find(reg_id, mw_kind);
1711
1712 Executor::SPtr executor = make_shared<Executor>();
1713- reg = RegistryObject::SPtr(new RegistryObject(*scope.death_observer, executor));
1714+ reg = RegistryObject::SPtr(new RegistryObject(*scope.death_observer, executor, mw));
1715 mw->add_registry_object(reg_id, reg);
1716 mw->add_state_receiver_object("StateReceiver", reg->state_receiver());
1717

Subscribers

People subscribed via source and target branches

to all changes: