Line data Source code
1 : /*
2 : * Packge : Wilt
3 : * Author : S. Hamblett <steve.hamblett@linux.com>
4 : * Date : 07/01/2014
5 : * Copyright : S.Hamblett@OSCF
6 : *
7 : * Change notification control class
8 : *
9 : * This class when constructed initiates change notification processing with either
10 : * a default set of change notification parameters or one supplied by the client.
11 : * When destroyed, change notification ceases.
12 : *
13 : *
14 : * The resulting notifications are turned into notification events and streamed to
15 : * the notification consumer. as a stream of WiltChangeNotificationEvent objects, see
16 : * this class for further details.
17 : *
18 : * CouchDb is initialized to supply the change notification stream in 'normal' mode, hence
19 : * this class requests the updates manually on a timed basis dependent on the heartberat period.
20 : *
21 : * Note that as form CouchDB 1.6.1 you must auth as an administrator with CouchDb
22 : * to allow notificatons to work, if you do not supply auth credentials before
23 : * starting notifications an exception is raised.
24 : */
25 :
26 : part of wilt;
27 :
28 : class _WiltChangeNotification {
29 : _WiltChangeNotification(
30 : this._host, this._port, this._scheme, this._httpAdapter,
31 1 : [this._dbName, this._parameters]) {
32 1 : if (_parameters == null) {
33 2 : _parameters = new WiltChangeNotificationParameters();
34 : }
35 :
36 3 : _sequence = _parameters.since;
37 :
38 : /**
39 : * Start the heartbeat timer
40 : */
41 : final Duration heartbeat =
42 3 : new Duration(milliseconds: _parameters.heartbeat);
43 3 : _timer = new Timer.periodic(heartbeat, _requestChanges);
44 :
45 : /**
46 : * Start change notifications
47 : */
48 2 : _requestChanges(_timer);
49 : }
50 :
51 : /// Parameters set
52 : WiltChangeNotificationParameters _parameters = null;
53 0 : WiltChangeNotificationParameters get parameters => _parameters;
54 : set parameters(WiltChangeNotificationParameters parameters) =>
55 0 : _parameters = parameters;
56 :
57 : /// Database name
58 : String _dbName = null;
59 :
60 : /// Host name
61 : String _host = null;
62 :
63 : /// Port number
64 : String _port = null;
65 :
66 : /// HTTP scheme
67 : String _scheme = null;
68 :
69 : /// Timer
70 : Timer _timer = null;
71 :
72 : /// Since sequence number
73 : int _sequence = 0;
74 :
75 : /// Paused indicator
76 : bool _paused = false;
77 1 : bool get pause => _paused;
78 1 : set pause(bool flag) => _paused = flag;
79 :
80 : WiltHTTPAdapter _httpAdapter = null;
81 :
82 : /// Change notification stream controller
83 : ///
84 : /// Populated with WiltChangeNotificationEvent events
85 : final StreamController _changeNotification = new StreamController.broadcast();
86 1 : StreamController get changeNotification => _changeNotification;
87 :
88 : /// Request the change notifications
89 : void _requestChanges(Timer timer) {
90 : /**
91 : * If paused return
92 : */
93 1 : if (_paused) return;
94 :
95 : /**
96 : * Create the URL from the parameters
97 : */
98 2 : final String sequence = _sequence.toString();
99 2 : final String path = "$_dbName/_changes?" +
100 1 : "&since=$sequence" +
101 3 : "&descending=${_parameters.descending}" +
102 3 : "&include_docs=${_parameters.includeDocs}" +
103 3 : "&attachments=${_parameters.includeAttachments}";
104 :
105 5 : final String url = "$_scheme$_host:${_port.toString()}/$path";
106 :
107 : /**
108 : * Open the request
109 : */
110 : try {
111 2 : _httpAdapter.getString(url)
112 1 : ..then((result) {
113 : /**
114 : * Process the change notification
115 : */
116 : try {
117 1 : final Map dbChange = JSON.decode(result);
118 1 : processDbChange(dbChange);
119 : } catch (e) {
120 : /**
121 : * Recoverable error, send the client an error event
122 : */
123 0 : print(
124 0 : "WiltChangeNotification::MonitorChanges JSON decode fail ${e.toString()}");
125 : final WiltChangeNotificationEvent notification =
126 0 : new WiltChangeNotificationEvent.decodeError(
127 0 : result, e.toString());
128 :
129 0 : _changeNotification.add(notification);
130 : }
131 : });
132 : } catch (e) {
133 : /**
134 : * Unrecoverable error, send the client an abort event
135 : */
136 0 : print(
137 0 : "WiltChangeNotification::MonitorChanges unable to contact CouchDB Error is ${e.toString()}");
138 : final WiltChangeNotificationEvent notification =
139 0 : new WiltChangeNotificationEvent.abort(e.toString());
140 :
141 0 : _changeNotification.add(notification);
142 : }
143 : }
144 :
145 : /// Database change updates
146 : void processDbChange(Map change) {
147 : /**
148 : * Check for an error response
149 : */
150 1 : if (change.containsKey('error')) {
151 : final WiltChangeNotificationEvent notification =
152 0 : new WiltChangeNotificationEvent.couchDbError(
153 0 : change['error'], change['reason']);
154 :
155 0 : _changeNotification.add(notification);
156 :
157 : return;
158 : }
159 :
160 : /**
161 : * Update the last sequence number
162 : */
163 2 : _sequence = change['last_seq'];
164 :
165 : /**
166 : * Process the result list
167 : */
168 1 : final List results = change['results'];
169 1 : if (results.isEmpty) {
170 : final WiltChangeNotificationEvent notification =
171 2 : new WiltChangeNotificationEvent.sequence(_sequence);
172 :
173 2 : _changeNotification.add(notification);
174 :
175 : return;
176 : }
177 :
178 1 : results.forEach((Map result) {
179 2 : final Map changes = result['changes'][0];
180 :
181 : /**
182 : * Check for delete or update
183 : */
184 1 : if (result.containsKey('deleted')) {
185 : final WiltChangeNotificationEvent notification =
186 1 : new WiltChangeNotificationEvent.delete(
187 3 : result['id'], changes['rev'], result['seq']);
188 :
189 2 : _changeNotification.add(notification);
190 : } else {
191 : jsonobject.JsonObject document = null;
192 1 : if (result.containsKey('doc')) {
193 2 : document = new jsonobject.JsonObject.fromMap(result['doc']);
194 : }
195 : final WiltChangeNotificationEvent notification =
196 1 : new WiltChangeNotificationEvent.update(
197 3 : result['id'], changes['rev'], result['seq'], document);
198 :
199 2 : _changeNotification.add(notification);
200 : }
201 : });
202 : }
203 :
204 : /// Stop change notifications
205 : void stopNotifications() {
206 2 : _timer.cancel();
207 : }
208 :
209 : /// Restart change notifications
210 : void restartChangeNotifications() {
211 : /**
212 : * Start the heartbeat timer
213 : */
214 : final Duration heartbeat =
215 3 : new Duration(milliseconds: _parameters.heartbeat);
216 3 : _timer = new Timer.periodic(heartbeat, _requestChanges);
217 :
218 : /**
219 : * Start change notifications
220 : */
221 2 : _requestChanges(_timer);
222 : }
223 : }
|