Skip to content

Commit 402d57c

Browse files
author
Tony Crisci
committed
Get rid of the Name class (breaking)
Move exporting interfaces directly onto the bus. Remove the `NameExistsError`. fixes #22
1 parent 35157b7 commit 402d57c

18 files changed

Lines changed: 347 additions & 460 deletions

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,6 @@ results
1717
node_modules
1818
npm-debug.log
1919
package-lock.json
20+
21+
*.swp
22+
*.swo

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -151,17 +151,17 @@ setTimeout(() => {
151151

152152
async function main() {
153153
// make a request for the name on the bus
154-
let name = await bus.requestName('org.test.name');
155-
// export the interface on the name
156-
name.export('/org/test/path', example);
154+
await bus.requestName('org.test.name');
155+
// export the interface on the path
156+
bus.export('/org/test/path', example);
157157
}
158158

159159
main().catch((err) => {
160160
console.log('Error: ' + err);
161161
});
162162
```
163163

164-
Interfaces extend the `Interface` class. Declare service methods, properties, and signals with the decorators provided from the library. First request a name on the bus with `bus.requestName()`. Then call `name.export()` with the path and interface to expose this interface on the bus.
164+
Interfaces extend the `Interface` class. Declare service methods, properties, and signals with the decorators provided from the library. You can optionally request a name on the bus with `bus.requestName()` so clients have a well-known name to connect to. Then call `bus.export()` with the path and interface to expose this interface on the bus.
165165

166166
Methods are called when a DBus client calls that method on the server. Properties can be gotten and set with the `org.freedesktop.DBus.Properties` interface and are included in the introspection xml.
167167

examples/service/index.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,9 @@ setTimeout(() => {
7878
}, 500);
7979

8080
async function main() {
81-
let name = await bus.requestName('org.test.name');
82-
name.export('/org/test/path', example);
83-
name.export('/org/test/path', example2);
81+
await bus.requestName('org.test.name');
82+
bus.export('/org/test/path', example);
83+
bus.export('/org/test/path', example2);
8484
}
8585

8686
main().catch((err) => {

index.js

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,88 @@ module.exports.DBUS_NAME_FLAG_REPLACE_EXISTING = constants.DBUS_NAME_FLAG_REPLAC
6161

6262
/**
6363
* A flag for {@link MessageBus#requestName} to indicate this request should
64-
* not enter the queue of clients requesting this name if it is taken. The
65-
* request may fail with {@link NameExistsError} in this case.
64+
* not enter the queue of clients requesting this name if it is taken.
6665
*
6766
* @see {@link https://dbus.freedesktop.org/doc/dbus-specification.html#bus-messages-request-name}
6867
* @constant DBUS_NAME_FLAG_DO_NOT_QUEUE
6968
*/
7069
module.exports.DBUS_NAME_FLAG_DO_NOT_QUEUE = constants.DBUS_NAME_FLAG_DO_NOT_QUEUE;
7170

71+
/**
72+
* An enum for the return value of {@link MessageBus#requestName} to indicate
73+
* the caller is now the primary owner of the name, replacing any previous
74+
* owner.
75+
*
76+
* @see {@link https://dbus.freedesktop.org/doc/dbus-specification.html#bus-messages-request-name}
77+
* @constant DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER
78+
*/
79+
module.exports.DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER = constants.DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER;
80+
81+
/**
82+
* An enum for the return value of {@link MessageBus#requestName} to indicate
83+
* the name already had an owner, `DBUS_NAME_FLAG_DO_NOT_QUEUE` was not
84+
* specified, and either the current owner did not specify
85+
* `DBUS_NAME_FLAG_ALLOW_REPLACEMENT` or the requesting application did not
86+
* specify `DBUS_NAME_FLAG_REPLACE_EXISTING`.
87+
*
88+
* @see {@link https://dbus.freedesktop.org/doc/dbus-specification.html#bus-messages-request-name}
89+
* @constant DBUS_REQUEST_NAME_REPLY_IN_QUEUE
90+
*/
91+
module.exports.DBUS_REQUEST_NAME_REPLY_IN_QUEUE = constants.DBUS_REQUEST_NAME_REPLY_IN_QUEUE;
92+
93+
/**
94+
* An enum for the return value of {@link MessageBus#requestName} to indicate
95+
* the name already has an owner, `DBUS_NAME_FLAG_DO_NOT_QUEUE` was specified,
96+
* and either `DBUS_NAME_FLAG_ALLOW_REPLACEMENT` was not specified by the
97+
* current owner, or `DBUS_NAME_FLAG_REPLACE_EXISTING` was not specified by the
98+
* requesting application.
99+
*
100+
* @see {@link https://dbus.freedesktop.org/doc/dbus-specification.html#bus-messages-request-name}
101+
* @constant DBUS_REQUEST_NAME_REPLY_EXISTS
102+
*/
103+
module.exports.DBUS_REQUEST_NAME_REPLY_EXISTS = constants.DBUS_REQUEST_NAME_REPLY_EXISTS;
104+
105+
/**
106+
* An enum for the return value of {@link MessageBus#requestName} to indicate
107+
* the application trying to request ownership of a name is already the owner
108+
* of it.
109+
*
110+
* @see {@link https://dbus.freedesktop.org/doc/dbus-specification.html#bus-messages-request-name}
111+
* @constant DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER
112+
*/
113+
module.exports.DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER = constants.DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER;
114+
115+
/**
116+
* An enumn for the return value of {@link MessageBus#releaseName} to indicate
117+
* the caller has released his claim on the given name. Either the caller was
118+
* the primary owner of the name, and the name is now unused or taken by
119+
* somebody waiting in the queue for the name, or the caller was waiting in the
120+
* queue for the name and has now been removed from the queue.
121+
*
122+
* @see {@link https://dbus.freedesktop.org/doc/dbus-specification.html#bus-messages-release-name}
123+
* @constant DBUS_RELEASE_NAME_REPLY_RELEASED
124+
*/
125+
module.exports.DBUS_RELEASE_NAME_REPLY_RELEASED = constants.DBUS_RELEASE_NAME_REPLY_RELEASED;
126+
127+
/**
128+
* An enumn for the return value of {@link MessageBus#releaseName} to indicate
129+
* the given name does not exist on this bus.
130+
*
131+
* @see {@link https://dbus.freedesktop.org/doc/dbus-specification.html#bus-messages-release-name}
132+
* @constant DBUS_RELEASE_NAME_REPLY_NON_EXISTENT
133+
*/
134+
module.exports.DBUS_RELEASE_NAME_REPLY_NON_EXISTENT = constants.DBUS_RELEASE_NAME_REPLY_NON_EXISTENT;
135+
136+
/**
137+
* An enumn for the return value of {@link MessageBus#releaseName} to indicate
138+
* the caller was not the primary owner of this name, and was also not waiting
139+
* in the queue to own this name.
140+
*
141+
* @see {@link https://dbus.freedesktop.org/doc/dbus-specification.html#bus-messages-release-name}
142+
* @constant DBUS_RELEASE_NAME_REPLY_NOT_OWNER
143+
*/
144+
module.exports.DBUS_RELEASE_NAME_REPLY_NOT_OWNER = constants.DBUS_RELEASE_NAME_REPLY_NOT_OWNER;
145+
72146
/**
73147
* An enum value for the {@link Message} `type` member to indicate the message
74148
* is a method call.
@@ -139,4 +213,3 @@ module.exports.Variant = Variant;
139213
module.exports.Message = Message;
140214
module.exports.validators = require('./lib/validators');
141215
module.exports.DBusError = errors.DBusError;
142-
module.exports.NameExistsError = errors.NameExistsError;

lib/bus.js

Lines changed: 123 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
const EventEmitter = require('events').EventEmitter;
22
const constants = require('./constants');
33
const handleMethod = require('./service/handlers');
4-
const { NameExistsError } = require('./errors');
5-
const Name = require('./service/name');
64
const {DBusError} = require('./errors');
75
const {Message} = require('./message-type');
6+
const ServiceObject = require('./service/object');
7+
const xml2js = require('xml2js');
88

99
let {
1010
assertBusNameValid,
@@ -15,6 +15,8 @@ let {
1515
let ProxyObject = require('./client/proxy-object');
1616
let { Interface } = require('./service/interface');
1717

18+
const xmlHeader = `<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">\n`
19+
1820
/**
1921
* @class
2022
* The `MessageBus` is a class for interacting with a DBus message bus capable
@@ -37,8 +39,8 @@ let { Interface } = require('./service/interface');
3739
* const bus = dbus.sessionBus();
3840
* // get a proxy object
3941
* let obj = await bus.getProxyObject('org.freedesktop.DBus', '/org/freedesktop/DBus');
40-
* // request a service name
41-
* let name = await bus.requestName('org.test.name');
42+
* // request a well-known name
43+
* await bus.requestName('org.test.name');
4244
*/
4345
class MessageBus extends EventEmitter {
4446
/**
@@ -48,13 +50,14 @@ class MessageBus extends EventEmitter {
4850
*/
4951
constructor(conn) {
5052
super();
53+
this._builder = new xml2js.Builder({ headless: true });
5154
this._connection = conn;
5255
this._serial = 1;
5356
this._methodReturnHandlers = {};
5457
this._signals = new EventEmitter();
55-
this._names = {};
5658
this._nameOwners = {};
5759
this._methodHandlers = [];
60+
this._serviceObjects = {}
5861
/**
5962
* The unique name of the bus connection. This will be `null` until the
6063
* `MessageBus` is connected.
@@ -139,7 +142,6 @@ class MessageBus extends EventEmitter {
139142
this.call(helloMessage)
140143
.then((msg) => {
141144
this.name = msg.body[0];
142-
// TODO document this signal
143145
this.emit('connect');
144146
})
145147
.catch((err) => {
@@ -164,20 +166,13 @@ class MessageBus extends EventEmitter {
164166
};
165167

166168
/**
167-
* Request a well-known [`Name`]{@link module:interface~Name} on the bus. The
168-
* `Name` can be used to export an [`Interface`]{@link
169-
* module:interface~Interface} at an object path on the name. If the name is
170-
* already taken and {@link DBUS_NAME_FLAG_DO_NOT_QUEUE} is given, the method
171-
* may throw a {@link NameExistsError}. See {@link
172-
* DBUS_NAME_FLAG_ALLOW_REPLACEMENT} and {@link
173-
* DBUS_NAME_FLAG_REPLACE_EXISTING} for more information on the other name
174-
* flags.
169+
* Request a well-known name on the bus.
170+
*
175171
* @see {@link https://dbus.freedesktop.org/doc/dbus-specification.html#bus-messages-request-name}
176172
*
177173
* @param name {string} - the well-known name on the bus to request.
178174
* @param flags {NameFlags} - DBus name flags which affect the behavior of taking the name.
179-
* @returns {Promise} - a Promise that resolves with the [`Name`]{@link
180-
* module:interface~Name}.
175+
* @returns {Promise} - a Promise that resolves with the `RequestNameReply`.
181176
*/
182177
requestName(name, flags) {
183178
flags = flags || 0;
@@ -193,25 +188,41 @@ class MessageBus extends EventEmitter {
193188
});
194189
this.call(requestNameMessage)
195190
.then((msg) => {
196-
let result = msg.body[0];
197-
if (result === constants.DBUS_REQUEST_NAME_REPLY_EXISTS) {
198-
return reject(new NameExistsError(`the name already exists: ${name}`));
199-
}
200-
if (this._names[name]) {
201-
let nameObj = this._names[name];
202-
nameObj.flags = flags;
203-
return resolve(nameObj);
204-
}
205-
let nameObj = new Name(this, name, flags);
206-
this._names[name] = nameObj;
207-
return resolve(nameObj);
191+
return resolve(msg.body[0]);
208192
})
209193
.catch((err) => {
210194
return reject(err);
211195
});
212196
});
213197
}
214198

199+
/**
200+
* Release this name. Requests that the name should no longer be owned by the
201+
* {@link MessageBus}.
202+
*
203+
* @returns {Promise} A Promise that will resolve with the `ReleaseNameReply`.
204+
*/
205+
releaseName(name) {
206+
return new Promise((resolve, reject) => {
207+
let msg = new Message({
208+
path: '/org/freedesktop/DBus',
209+
destination: 'org.freedesktop.DBus',
210+
interface: 'org.freedesktop.DBus',
211+
member: 'ReleaseName',
212+
signature: 's',
213+
body: [name]
214+
});
215+
this.call(msg)
216+
.then((reply) => {
217+
return resolve(reply.body[0]);
218+
})
219+
.catch((err) => {
220+
return reject(err);
221+
});
222+
});
223+
}
224+
225+
215226
/**
216227
* Disconnect this `MessageBus` from the bus.
217228
*/
@@ -333,6 +344,90 @@ class MessageBus extends EventEmitter {
333344
this._connection.message(msg);
334345
}
335346

347+
/**
348+
* Export an [`Interface`]{@link module:interface~Interface} on the bus. See
349+
* the documentation for that class for how to define service interfaces.
350+
*
351+
* @param path {string} - The object path to export this `Interface` on.
352+
* @param iface {module:interface~Interface} - The service interface to export.
353+
*/
354+
export(path, iface) {
355+
let obj = this._getServiceObject(path);
356+
obj.addInterface(iface);
357+
}
358+
359+
/**
360+
* Unexport an `Interface` on the bus. The interface will no longer be
361+
* advertised to clients.
362+
*
363+
* @param {string} path - The object path on which to unexport.
364+
* @param {module:interface~Interface} [iface] - The `Interface` to unexport.
365+
* If not given, this will remove all interfaces on the path.
366+
*/
367+
unexport(path, iface) {
368+
iface = iface || null;
369+
if (iface === null) {
370+
this._removeServiceObject(path);
371+
} else {
372+
let obj = this._getServiceObject(path);
373+
obj.removeInterface(iface);
374+
if (!obj.interfaces.length) {
375+
this._removeServiceObject(path);
376+
}
377+
}
378+
}
379+
380+
_introspect(path) {
381+
assertObjectPathValid(path);
382+
let xml = {
383+
node: {
384+
node: []
385+
}
386+
};
387+
388+
if (this._serviceObjects[path]) {
389+
xml.node.interface = this._serviceObjects[path].introspect();
390+
}
391+
392+
let pathSplit = path.split('/').filter(n => n);
393+
394+
for (let key of Object.keys(this._serviceObjects)) {
395+
let keySplit = key.split('/').filter(n => n);
396+
if (keySplit.length <= pathSplit.length) {
397+
continue;
398+
}
399+
if (pathSplit.every((v, i) => v === keySplit[i])) {
400+
let child = keySplit[pathSplit.length];
401+
xml.node.node.push({
402+
$: {
403+
name: child
404+
}
405+
});
406+
}
407+
}
408+
409+
return xmlHeader + this._builder.buildObject(xml);
410+
}
411+
412+
_getServiceObject(path) {
413+
assertObjectPathValid(path);
414+
if (!this._serviceObjects[path]) {
415+
this._serviceObjects[path] = new ServiceObject(path, this);
416+
}
417+
return this._serviceObjects[path];
418+
}
419+
420+
_removeServiceObject(path) {
421+
assertObjectPathValid(path);
422+
if (this._serviceObjects[path]) {
423+
let obj = this._serviceObjects[path];
424+
for (let i of Object.keys(obj.interfaces)) {
425+
obj.removeInterface(obj.interfaces[i]);
426+
}
427+
delete this._serviceObjects[path];
428+
}
429+
}
430+
336431
_addMatch(match) {
337432
let msg = new Message({
338433
path: '/org/freedesktop/DBus',

lib/constants.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ module.exports = {
1515
DBUS_REQUEST_NAME_REPLY_EXISTS: 3,
1616
DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER: 4,
1717

18+
// the result of bus.releaseName()
19+
DBUS_RELEASE_NAME_REPLY_RELEASED: 1,
20+
DBUS_RELEASE_NAME_REPLY_NON_EXISTENT: 2,
21+
DBUS_RELEASE_NAME_REPLY_NOT_OWNER: 3,
22+
1823
messageType: {
1924
INVALID: 0,
2025
METHOD_CALL: 1,

lib/errors.js

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,23 +26,6 @@ class DBusError extends Error {
2626
}
2727
}
2828

29-
/**
30-
* An error that can be thrown when trying to request a name with {@link
31-
* MessageBus#requestName} when the {@link DBUS_NAME_FLAG_DO_NOT_QUEUE} flag is
32-
* given.
33-
*/
34-
class NameExistsError extends Error {
35-
/**
36-
* Create a `NameExistsError`. This constructor should not be called directly.
37-
*/
38-
constructor(message) {
39-
message = message || 'Requested a name that already exists on the bus';
40-
super(message);
41-
this.name = 'NameExistsError';
42-
}
43-
}
44-
4529
module.exports = {
4630
DBusError: DBusError,
47-
NameExistsError: NameExistsError,
4831
};

0 commit comments

Comments
 (0)