1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
6 * @fileoverview A class for managing all enumerated gnubby devices.
20 * isSharedAccess: boolean,
21 * enumerate: function(function(Array)),
22 * deviceToDeviceId: function(*): GnubbyDeviceId,
23 * open: function(Gnubbies, number, *, function(number, GnubbyDevice=))
26 var GnubbyNamespaceImpl
;
29 * Manager of opened devices.
33 /** @private {Object<string, Array>} */
35 this.pendingEnumerate
= []; // clients awaiting an enumerate
37 * The distinct namespaces registered in this Gnubbies instance, in order of
39 * @private {Array<string>}
41 this.namespaces_
= [];
42 /** @private {Object<string, GnubbyNamespaceImpl>} */
44 /** @private {Object<string, Object<number, !GnubbyDevice>>} */
46 /** @private {Object<string, Object<number, *>>} */
47 this.pendingOpens_
= {}; // clients awaiting an open
51 * Registers a new gnubby namespace, i.e. an implementation of the
52 * enumerate/open functions for all devices within a namespace.
53 * @param {string} namespace The namespace of the numerator, e.g. 'usb'.
54 * @param {GnubbyNamespaceImpl} impl The implementation.
56 Gnubbies
.prototype.registerNamespace = function(namespace, impl
) {
57 if (!this.impl_
.hasOwnProperty(namespace)) {
58 this.namespaces_
.push(namespace);
60 this.impl_
[namespace] = impl
;
64 * @param {GnubbyDeviceId} id The device id.
65 * @return {boolean} Whether the device is a shared access device.
67 Gnubbies
.prototype.isSharedAccess = function(id
) {
68 if (!this.impl_
.hasOwnProperty(id
.namespace)) return false;
69 return this.impl_
[id
.namespace].isSharedAccess
;
73 * @param {GnubbyDeviceId} which The device to remove.
75 Gnubbies
.prototype.removeOpenDevice = function(which
) {
76 if (this.openDevs_
[which
.namespace] &&
77 this.openDevs_
[which
.namespace].hasOwnProperty(which
.device
)) {
78 delete this.openDevs_
[which
.namespace][which
.device
];
82 /** Close all enumerated devices. */
83 Gnubbies
.prototype.closeAll = function() {
84 if (this.inactivityTimer
) {
85 this.inactivityTimer
.clearTimeout();
86 this.inactivityTimer
= undefined;
88 // Close and stop talking to any gnubbies we have enumerated.
89 for (var namespace in this.openDevs_
) {
90 for (var dev
in this.openDevs_
[namespace]) {
91 var deviceId
= Number(dev
);
92 this.openDevs_
[namespace][deviceId
].destroy();
100 * @param {function(number, Array<GnubbyDeviceId>)} cb Called back with the
101 * result of enumerating.
103 Gnubbies
.prototype.enumerate = function(cb
) {
105 cb = function(rc
, indexes
) {
106 var msg
= 'defaultEnumerateCallback(' + rc
;
109 for (var i
= 0; i
< indexes
.length
; i
++) {
110 msg
+= JSON
.stringify(indexes
[i
]);
115 console
.log(UTIL_fmt(msg
));
119 if (!this.namespaces_
.length
) {
120 cb(-GnubbyDevice
.OK
, []);
124 var namespacesEnumerated
= 0;
128 * @param {string} namespace The namespace that was enumerated.
129 * @param {Array<GnubbyDeviceId>} existingDeviceIds Previously enumerated
130 * device IDs (from other namespaces), if any.
131 * @param {Array} devs The devices in the namespace.
133 function enumerated(namespace, existingDeviceIds
, devs
) {
134 namespacesEnumerated
++;
135 var lastNamespace
= (namespacesEnumerated
== self
.namespaces_
.length
);
137 if (chrome
.runtime
.lastError
) {
138 console
.warn(UTIL_fmt('lastError: ' + chrome
.runtime
.lastError
));
139 console
.log(chrome
.runtime
.lastError
);
143 console
.log(UTIL_fmt('Enumerated ' + devs
.length
+ ' gnubbies'));
146 var presentDevs
= {};
148 var deviceToDeviceId
= self
.impl_
[namespace].deviceToDeviceId
;
149 for (var i
= 0; i
< devs
.length
; ++i
) {
150 var deviceId
= deviceToDeviceId(devs
[i
]);
151 deviceIds
.push(deviceId
);
152 presentDevs
[deviceId
.device
] = devs
[i
];
156 for (var dev
in self
.openDevs_
[namespace]) {
157 if (!presentDevs
.hasOwnProperty(dev
)) {
162 for (var i
= 0; i
< toRemove
.length
; i
++) {
164 if (self
.openDevs_
[namespace][dev
]) {
165 self
.openDevs_
[namespace][dev
].destroy();
166 delete self
.openDevs_
[namespace][dev
];
170 self
.devs_
[namespace] = devs
;
171 existingDeviceIds
.push
.apply(existingDeviceIds
, deviceIds
);
173 while (self
.pendingEnumerate
.length
!= 0) {
174 var cb
= self
.pendingEnumerate
.shift();
175 cb(-GnubbyDevice
.OK
, existingDeviceIds
);
181 function makeEnumerateCb(namespace) {
182 return function(devs
) {
183 enumerated(namespace, deviceIds
, devs
);
187 this.pendingEnumerate
.push(cb
);
188 if (this.pendingEnumerate
.length
== 1) {
189 for (var i
= 0; i
< this.namespaces_
.length
; i
++) {
190 var namespace = this.namespaces_
[i
];
191 var enumerator
= this.impl_
[namespace].enumerate
;
192 enumerator(makeEnumerateCb(namespace));
198 * Amount of time past last activity to set the inactivity timer to, in millis.
201 Gnubbies
.INACTIVITY_TIMEOUT_MARGIN_MILLIS
= 30000;
204 * @param {number|undefined} opt_timeoutMillis Timeout in milliseconds
206 Gnubbies
.prototype.resetInactivityTimer = function(opt_timeoutMillis
) {
207 var millis
= opt_timeoutMillis
?
208 opt_timeoutMillis
+ Gnubbies
.INACTIVITY_TIMEOUT_MARGIN_MILLIS
:
209 Gnubbies
.INACTIVITY_TIMEOUT_MARGIN_MILLIS
;
210 if (!this.inactivityTimer
) {
211 this.inactivityTimer
=
212 new CountdownTimer(millis
, this.inactivityTimeout_
.bind(this));
213 } else if (millis
> this.inactivityTimer
.millisecondsUntilExpired()) {
214 this.inactivityTimer
.clearTimeout();
215 this.inactivityTimer
.setTimeout(millis
, this.inactivityTimeout_
.bind(this));
220 * Called when the inactivity timeout expires.
223 Gnubbies
.prototype.inactivityTimeout_ = function() {
224 this.inactivityTimer
= undefined;
225 for (var namespace in this.openDevs_
) {
226 for (var dev
in this.openDevs_
[namespace]) {
227 var deviceId
= Number(dev
);
228 console
.warn(namespace + ' device ' + deviceId
+
229 ' still open after inactivity, closing');
230 this.openDevs_
[namespace][deviceId
].destroy();
236 * Opens and adds a new client of the specified device.
237 * @param {GnubbyDeviceId} which Which device to open.
238 * @param {*} who Client of the device.
239 * @param {function(number, GnubbyDevice=)} cb Called back with the result of
240 * opening the device.
242 Gnubbies
.prototype.addClient = function(which
, who
, cb
) {
243 this.resetInactivityTimer();
247 function opened(gnubby
, who
, cb
) {
248 if (gnubby
.closing
) {
249 // Device is closing or already closed.
250 self
.removeClient(gnubby
, who
);
251 if (cb
) { cb(-GnubbyDevice
.NODEVICE
); }
253 gnubby
.registerClient(who
);
254 if (cb
) { cb(-GnubbyDevice
.OK
, gnubby
); }
258 function notifyOpenResult(rc
) {
259 if (self
.pendingOpens_
[which
.namespace]) {
260 while (self
.pendingOpens_
[which
.namespace][which
.device
].length
!= 0) {
261 var client
= self
.pendingOpens_
[which
.namespace][which
.device
].shift();
264 delete self
.pendingOpens_
[which
.namespace][which
.device
];
269 var deviceToDeviceId
= this.impl_
[which
.namespace].deviceToDeviceId
;
270 if (this.devs_
[which
.namespace]) {
271 for (var i
= 0; i
< this.devs_
[which
.namespace].length
; i
++) {
272 var device
= this.devs_
[which
.namespace][i
];
273 if (deviceToDeviceId(device
).device
== which
.device
) {
280 // Index out of bounds. Device does not exist in current enumeration.
281 this.removeClient(null, who
);
282 if (cb
) { cb(-GnubbyDevice
.NODEVICE
); }
286 function openCb(rc
, opt_gnubby
) {
288 notifyOpenResult(rc
);
292 notifyOpenResult(-GnubbyDevice
.NODEVICE
);
295 var gnubby
= /** @type {!GnubbyDevice} */ (opt_gnubby
);
296 if (!self
.openDevs_
[which
.namespace]) {
297 self
.openDevs_
[which
.namespace] = {};
299 self
.openDevs_
[which
.namespace][which
.device
] = gnubby
;
300 while (self
.pendingOpens_
[which
.namespace][which
.device
].length
!= 0) {
301 var client
= self
.pendingOpens_
[which
.namespace][which
.device
].shift();
302 opened(gnubby
, client
.who
, client
.cb
);
304 delete self
.pendingOpens_
[which
.namespace][which
.device
];
307 if (this.openDevs_
[which
.namespace] &&
308 this.openDevs_
[which
.namespace].hasOwnProperty(which
.device
)) {
309 var gnubby
= this.openDevs_
[which
.namespace][which
.device
];
310 opened(gnubby
, who
, cb
);
312 var opener
= {who
: who
, cb
: cb
};
313 if (!this.pendingOpens_
.hasOwnProperty(which
.namespace)) {
314 this.pendingOpens_
[which
.namespace] = {};
316 if (this.pendingOpens_
[which
.namespace].hasOwnProperty(which
.device
)) {
317 this.pendingOpens_
[which
.namespace][which
.device
].push(opener
);
319 this.pendingOpens_
[which
.namespace][which
.device
] = [opener
];
320 var openImpl
= this.impl_
[which
.namespace].open
;
321 openImpl(this, which
.device
, dev
, openCb
);
327 * Removes a client from a low-level gnubby.
328 * @param {GnubbyDevice} whichDev The gnubby.
329 * @param {*} who The client.
331 Gnubbies
.prototype.removeClient = function(whichDev
, who
) {
332 console
.log(UTIL_fmt('Gnubbies.removeClient()'));
334 this.resetInactivityTimer();
336 // De-register client from all known devices.
337 for (var namespace in this.openDevs_
) {
338 for (var devId
in this.openDevs_
[namespace]) {
339 var deviceId
= Number(devId
);
340 var dev
= this.openDevs_
[namespace][deviceId
];
341 if (dev
.hasClient(who
)) {
342 if (whichDev
&& dev
!= whichDev
) {
343 console
.warn('Gnubby attached to more than one device!?');
345 if (!dev
.deregisterClient(who
)) {