Merge lp:~jderose/dmedia/dbus3 into lp:dmedia
- dbus3
- Merge into trunk
Proposed by
Jason Gerard DeRose
Status: | Merged |
---|---|
Merged at revision: | 291 |
Proposed branch: | lp:~jderose/dmedia/dbus3 |
Merge into: | lp:dmedia |
Diff against target: |
1259 lines (+489/-546) 17 files modified
debian/control (+2/-0) dmedia-browser (+3/-1) dmedia-cli (+146/-230) dmedia-gtk (+1/-1) dmedia-service (+132/-123) dmedia-service3 (+0/-181) dmedia/__init__.py (+1/-0) dmedia/constants.py (+1/-3) dmedia/core.py (+6/-0) dmedia/service/api.py (+2/-2) dmedia/service/avahi.py (+116/-0) dmedia/transfers.py (+2/-2) dmedia/units.py (+13/-0) setup.py (+1/-2) share/org.freedesktop.Dmedia.service (+1/-1) test-cli.py (+33/-0) test-service.py (+29/-0) |
To merge this branch: | bzr merge lp:~jderose/dmedia/dbus3 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
dmedia Dev | Pending | ||
Review via email: mp+88806@code.launchpad.net |
Commit message
Description of the change
The actual porting to python3-dbus was tiny, but then I also had to merge everything into a single real service, which is what most of the change is.
I also did a bit of cleanup and refinement:
1. Renamed DBus bus name from 'org.freedeskto
2. Moved Avahi integration into new dmedia.
3. Revamped the `dmedia-cli` script to be simpler and more maintainable, yet hopefully retain all the good UX enhancements that David Green did
4. Added Dmedia.Resolve() DBus method... this is needed for the Novacut render server, and is the start of the API 3rd party apps will use to integrate with Dmedia
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'debian/control' | |||
2 | --- debian/control 2011-12-29 07:01:47 +0000 | |||
3 | +++ debian/control 2012-01-17 04:20:28 +0000 | |||
4 | @@ -11,6 +11,8 @@ | |||
5 | 11 | Package: dmedia | 11 | Package: dmedia |
6 | 12 | Architecture: all | 12 | Architecture: all |
7 | 13 | Depends: ${misc:Depends}, python3 (>= 3.2), | 13 | Depends: ${misc:Depends}, python3 (>= 3.2), |
8 | 14 | python3-gi | python3-gobject, | ||
9 | 15 | python3-dbus, | ||
10 | 14 | python3-filestore (>= 12.01), | 16 | python3-filestore (>= 12.01), |
11 | 15 | python3-microfiber (>= 12.01), | 17 | python3-microfiber (>= 12.01), |
12 | 16 | python3-userwebkit (>= 12.01), | 18 | python3-userwebkit (>= 12.01), |
13 | 17 | 19 | ||
14 | === modified file 'dmedia-browser' | |||
15 | --- dmedia-browser 2012-01-15 15:32:45 +0000 | |||
16 | +++ dmedia-browser 2012-01-17 04:20:28 +0000 | |||
17 | @@ -24,18 +24,20 @@ | |||
18 | 24 | from urllib.parse import urlparse | 24 | from urllib.parse import urlparse |
19 | 25 | 25 | ||
20 | 26 | from userwebkit import BaseApp | 26 | from userwebkit import BaseApp |
21 | 27 | import dmedia | ||
22 | 27 | from dmedia import local | 28 | from dmedia import local |
23 | 28 | 29 | ||
24 | 29 | 30 | ||
25 | 30 | class App(BaseApp): | 31 | class App(BaseApp): |
26 | 31 | name = 'dmedia' | 32 | name = 'dmedia' |
27 | 32 | dbname = 'dmedia-0' | 33 | dbname = 'dmedia-0' |
28 | 34 | version = dmedia.__version__ | ||
29 | 33 | title = 'Dmedia Browser' | 35 | title = 'Dmedia Browser' |
30 | 34 | 36 | ||
31 | 35 | splash = 'splash.html' | 37 | splash = 'splash.html' |
32 | 36 | page = 'browser.html' | 38 | page = 'browser.html' |
33 | 37 | 39 | ||
35 | 38 | proxy_bus = 'org.freedesktop.DMedia' | 40 | proxy_bus = dmedia.BUS |
36 | 39 | local = None | 41 | local = None |
37 | 40 | 42 | ||
38 | 41 | def dmedia_resolver(self, uri): | 43 | def dmedia_resolver(self, uri): |
39 | 42 | 44 | ||
40 | === modified file 'dmedia-cli' | |||
41 | --- dmedia-cli 2011-10-24 21:22:09 +0000 | |||
42 | +++ dmedia-cli 2012-01-17 04:20:28 +0000 | |||
43 | @@ -27,234 +27,150 @@ | |||
44 | 27 | Command line tool for talking to dmedia DBus services. | 27 | Command line tool for talking to dmedia DBus services. |
45 | 28 | """ | 28 | """ |
46 | 29 | 29 | ||
48 | 30 | from sys import argv, stderr | 30 | import optparse |
49 | 31 | from collections import OrderedDict | ||
50 | 32 | import sys | ||
51 | 33 | from gettext import ngettext | ||
52 | 34 | from os import path | ||
53 | 35 | |||
54 | 36 | import dbus | ||
55 | 37 | |||
56 | 31 | import dmedia | 38 | import dmedia |
286 | 32 | from dmedia.service.api import DMedia | 39 | from dmedia.units import minsec |
287 | 33 | 40 | ||
288 | 34 | class DBusCLI (object): | 41 | |
289 | 35 | """ | 42 | methods = OrderedDict() |
290 | 36 | DBusCLI allows you to easily create command line interfaces from | 43 | session = dbus.SessionBus() |
291 | 37 | DBus APIs. | 44 | |
292 | 38 | 45 | ||
293 | 39 | Example usage: | 46 | def error(msg, code=1): |
294 | 40 | my_cli = DBusCLI( | 47 | print('ERROR:', msg) |
295 | 41 | 'mycli', '1.0', 'org.freedesktop.My', | 48 | sys.exit(code) |
296 | 42 | '/', 'org.freedesktop.My' | 49 | |
297 | 43 | ) | 50 | |
298 | 44 | my_cli.add_action("name1", function1, "usage of name1", "description of name1") | 51 | def print_methods(): |
299 | 45 | my_cli.add_dbus_action("MethodName", "usage of MethodName", "description of MethodName") | 52 | print('DBus methods on {}:'.format(dmedia.BUS)) |
300 | 46 | if __name__ == '__main__': | 53 | width = max(len(name) for name in methods) |
301 | 47 | my_cli.main(*sys.argv[1:]) | 54 | for name in methods: |
302 | 48 | """ | 55 | cls = methods[name] |
303 | 49 | def __init__(self, exec_name, version): | 56 | print(' {} {}'.format(name.ljust(width), cls.__doc__)) |
304 | 50 | """ | 57 | |
305 | 51 | `exec_name` is the name of the executable of the command line | 58 | |
306 | 52 | program. It should be what the user needs to run to use the | 59 | def print_usage(cls): |
307 | 53 | program. | 60 | print('Usage:') |
308 | 54 | 61 | print(' ', *cls.usage()) | |
309 | 55 | `version` is the version string of the command line program. | 62 | |
310 | 56 | 63 | ||
311 | 57 | `dbus_bus` is the dbus bus name of the dbus API. | 64 | class MethodMeta(type): |
312 | 58 | 65 | def __new__(meta, name, bases, dict): | |
313 | 59 | `dbus_object` is the dbus object of the dbus API. | 66 | cls = type.__new__(meta, name, bases, dict) |
314 | 60 | 67 | if not name.startswith('_'): | |
315 | 61 | `dbus_iface` is the dbus interface of the dbus API. | 68 | methods[name] = cls |
316 | 62 | """ | 69 | return cls |
317 | 63 | self._exec = exec_name | 70 | |
318 | 64 | self._version = version | 71 | |
319 | 65 | self._action_methods = {} | 72 | class _Method(metaclass=MethodMeta): |
320 | 66 | self._action_help = {} | 73 | args = tuple() |
321 | 67 | self._add_default_actions() | 74 | |
322 | 68 | self._dmedia = DMedia() | 75 | def __init__(self, bus): |
323 | 69 | 76 | self.bus = bus | |
324 | 70 | def has_action(self, name): | 77 | self.proxy = session.get_object(bus, '/') |
325 | 71 | """ | 78 | |
326 | 72 | Return true if the action `name` exists, | 79 | @classmethod |
327 | 73 | false otherwise. | 80 | def usage(cls): |
328 | 74 | """ | 81 | script = path.basename(sys.argv[0]) |
329 | 75 | return (name in self._action_methods and name in self._action_help) | 82 | cmd = [script, cls.__name__] |
330 | 76 | 83 | cmd.extend(arg.upper() for arg in cls.args) | |
331 | 77 | def get_action_method(self, name): | 84 | return cmd |
332 | 78 | """ | 85 | |
333 | 79 | Return the function for action `name`. | 86 | def run(self, args): |
334 | 80 | """ | 87 | args = self.validate_args(*args) |
335 | 81 | return self._action_methods[name] | 88 | method = self.proxy.get_dbus_method(self.__class__.__name__) |
336 | 82 | 89 | return self.format_output(method(*args)) | |
337 | 83 | def get_usage(self): | 90 | |
338 | 84 | """ | 91 | def validate_args(self, *args): |
339 | 85 | Return the usage string for this program. | 92 | return args |
340 | 86 | """ | 93 | |
341 | 87 | return """Usage: | 94 | def format_output(self, output): |
342 | 88 | \t%s ACTION [ARGUMENTS] | 95 | return output |
343 | 89 | 96 | ||
344 | 90 | Run: | 97 | |
345 | 91 | \t%s help ACTION | 98 | class Version(_Method): |
346 | 92 | for help with a specific action. | 99 | 'Show version of running `dmedia-service`' |
347 | 93 | 100 | ||
348 | 94 | Run: | 101 | |
349 | 95 | \t%s actions | 102 | class Kill(_Method): |
350 | 96 | for a list of available actions. | 103 | 'Shutdown `dmedia-service`' |
351 | 97 | """ % (self._exec, self._exec, self._exec) | 104 | |
352 | 98 | 105 | def format_output(self, seconds): | |
353 | 99 | def get_help(self, *names): | 106 | return '{} was running for {}'.format(self.bus, minsec(seconds)) |
354 | 100 | """ | 107 | |
355 | 101 | Return the help string for action(s) `names`. | 108 | |
356 | 102 | If no names are specified, the help string will show | 109 | class GetEnv(_Method): |
357 | 103 | help for all available actions. | 110 | 'Get the CouchDB and Dmedia environment info' |
358 | 104 | """ | 111 | |
359 | 105 | if len(names) == 0: | 112 | |
360 | 106 | names = self.get_actions() | 113 | class LocalDmedia(_Method): |
361 | 107 | return "\n".join(map( | 114 | 'Get the _local/dmedia document (shows file-stores)' |
362 | 108 | lambda name: "Action `%s`. Usage:\n\t%s %s %s\n%s\n" % ( | 115 | |
363 | 109 | (name, self._exec, name) + self._action_help[name] | 116 | |
364 | 110 | ), | 117 | class LocalPeers(_Method): |
365 | 111 | names | 118 | 'Get the _local/peers document' |
366 | 112 | )) | 119 | |
367 | 113 | 120 | ||
368 | 114 | def get_actions(self): | 121 | class AddFileStore(_Method): |
369 | 115 | """ | 122 | 'Add a file storage location' |
370 | 116 | Return all available actions as a tuple. | 123 | |
371 | 117 | """ | 124 | args = ['directory'] |
372 | 118 | return tuple(self._action_methods) | 125 | |
373 | 119 | 126 | def validate_args(self, directory): | |
374 | 120 | def show_usage(self): | 127 | return [path.abspath(directory)] |
375 | 121 | """ | 128 | |
376 | 122 | Display the usage string for this program. | 129 | |
377 | 123 | """ | 130 | class RemoveFileStore(AddFileStore): |
378 | 124 | print(self.get_usage()) | 131 | 'Add a file storage location' |
379 | 125 | 132 | ||
380 | 126 | def show_help(self, *names): | 133 | |
381 | 127 | """ | 134 | class Resolve(_Method): |
382 | 128 | Display the help string for `names`. See `get_help`. | 135 | 'Resolve Dmedia file ID into a regular file path' |
383 | 129 | """ | 136 | |
384 | 130 | print(self.get_help(*names)) | 137 | args = ['file_id'] |
385 | 131 | 138 | ||
386 | 132 | def show_actions(self): | 139 | |
387 | 133 | """ | 140 | parser = optparse.OptionParser( |
388 | 134 | Display all available actions, each on a separate line. | 141 | usage='%prog METHOD [ARGS...]', |
389 | 135 | """ | 142 | version=dmedia.__version__, |
390 | 136 | print("\n".join(self.get_actions())) | 143 | ) |
391 | 137 | 144 | parser.add_option('--bus', | |
392 | 138 | def show_version(self): | 145 | help='DBus bus name; default is {!r}'.format(dmedia.BUS), |
393 | 139 | """ | 146 | default=dmedia.BUS |
394 | 140 | Display the version of this program. | 147 | ) |
395 | 141 | """ | 148 | (options, args) = parser.parse_args() |
396 | 142 | print(self._version) | 149 | |
397 | 143 | 150 | ||
398 | 144 | def dbus_run(self, name, *args): | 151 | if len(args) == 0: |
399 | 145 | """ | 152 | parser.print_help() |
400 | 146 | Run the dbus method `name` with arguments `args`. | 153 | print('') |
401 | 147 | Catch any errors and print them to stderr. | 154 | print_methods() |
402 | 148 | """ | 155 | sys.exit(0) |
403 | 149 | try: | 156 | |
404 | 150 | method = getattr(self._dmedia, name) | 157 | name = args[0] |
405 | 151 | print(str(method(*args))) | 158 | args = args[1:] |
406 | 152 | except Exception as e: | 159 | if name not in methods: |
407 | 153 | stderr.write("Error: {}\n".format(e)) | 160 | print_methods() |
408 | 154 | 161 | print('') | |
409 | 155 | def run_action(self, name, *args): | 162 | error('Unknown method {!r}'.format(name)) |
410 | 156 | """ | 163 | |
411 | 157 | Run the action `name` with arguments `args`. | 164 | cls = methods[name] |
412 | 158 | """ | 165 | if len(args) != len(cls.args): |
413 | 159 | if self.has_action(name): | 166 | print_usage(cls) |
414 | 160 | self.get_action_method(name)(*args) | 167 | print('') |
415 | 161 | else: | 168 | msg = ngettext( |
416 | 162 | stderr.write("No such action `%s`\n" % name) | 169 | '{!r} takes exactly {} argument', |
417 | 163 | print(self.get_usage()) | 170 | '{!r} takes exactly {} arguments', |
418 | 164 | 171 | len(cls.args) | |
419 | 165 | def run_action_with_bus(self, bus, name, *args): | 172 | ) |
420 | 166 | """ | 173 | error(msg.format(name, len(cls.args))) |
421 | 167 | Run the action `name` with arguments `args`. | 174 | |
422 | 168 | Use a custom dbus bus name `bus`. | 175 | method = cls(options.bus) |
423 | 169 | """ | 176 | print(method.run(args)) |
195 | 170 | self._bus = bus | ||
196 | 171 | self.run_action(name, *args) | ||
197 | 172 | |||
198 | 173 | def add_action(self, name, function=lambda *a:None, usage="", description=""): | ||
199 | 174 | """ | ||
200 | 175 | Add an action to the command line program. | ||
201 | 176 | `name` is the name of the action. | ||
202 | 177 | `function` is the function to be called for this action. | ||
203 | 178 | `usage` is a string describing the arguments of the action to the user. | ||
204 | 179 | `description` is a string describing what the action does. | ||
205 | 180 | """ | ||
206 | 181 | self._action_methods[name] = function | ||
207 | 182 | self._action_help[name] = (usage, description) | ||
208 | 183 | |||
209 | 184 | def add_dbus_action(self, name, usage="", description=""): | ||
210 | 185 | """ | ||
211 | 186 | Add an action to the command line program based upon a dbus method. | ||
212 | 187 | `name` is the name of the action and the name of the dbus method. | ||
213 | 188 | `usage` is a string describing the arguments of the action to the user. | ||
214 | 189 | `description` is a string describing what the action does. | ||
215 | 190 | """ | ||
216 | 191 | self.add_action( | ||
217 | 192 | name, | ||
218 | 193 | lambda *args:self.dbus_run(name, *args), | ||
219 | 194 | usage, | ||
220 | 195 | description | ||
221 | 196 | ) | ||
222 | 197 | |||
223 | 198 | def _add_default_actions(self): | ||
224 | 199 | #version | ||
225 | 200 | self.add_action( | ||
226 | 201 | "version", | ||
227 | 202 | self.show_version, | ||
228 | 203 | "", | ||
229 | 204 | "Display the version of this program." | ||
230 | 205 | ) | ||
231 | 206 | #help | ||
232 | 207 | self.add_action( | ||
233 | 208 | "help", | ||
234 | 209 | self.show_help, | ||
235 | 210 | "[ACTION_1] [ACTION_2] ... [ACTION_N]", | ||
236 | 211 | "Display help information for each of the listed actions. If no actions are listed, help will be displayed for all available actions." | ||
237 | 212 | ) | ||
238 | 213 | #usage | ||
239 | 214 | self.add_action( | ||
240 | 215 | "usage", | ||
241 | 216 | self.show_usage, | ||
242 | 217 | "", | ||
243 | 218 | "Display a usage message." | ||
244 | 219 | ) | ||
245 | 220 | #actions | ||
246 | 221 | self.add_action( | ||
247 | 222 | "actions", | ||
248 | 223 | self.show_actions, | ||
249 | 224 | "", | ||
250 | 225 | "Display the list of available actions." | ||
251 | 226 | ) | ||
252 | 227 | #bus | ||
253 | 228 | self.add_action( | ||
254 | 229 | "bus", | ||
255 | 230 | self.run_action_with_bus, | ||
256 | 231 | "BUS_NAME ACTION [ARGUMENTS]", | ||
257 | 232 | "Run an action using the dbus bus `BUS_NAME` for any dbus interaction." | ||
258 | 233 | ) | ||
259 | 234 | |||
260 | 235 | def main(self, *args): | ||
261 | 236 | """ | ||
262 | 237 | Run the command line program with the specified arguments. | ||
263 | 238 | """ | ||
264 | 239 | args = list(args) | ||
265 | 240 | if len(args) < 1: | ||
266 | 241 | self.run_action("usage") | ||
267 | 242 | else: | ||
268 | 243 | self.run_action(args.pop(0), *args) | ||
269 | 244 | |||
270 | 245 | dmedia_cli = DBusCLI("dmedia-cli", dmedia.__version__) | ||
271 | 246 | |||
272 | 247 | dmedia_cli.add_dbus_action("Version", "", "Display the version of running `dmedia-service`.") | ||
273 | 248 | dmedia_cli.add_dbus_action("Kill", "", "Shutdown `dmedia-service`.") | ||
274 | 249 | dmedia_cli.add_dbus_action("AddFileStore", "PARENT_DIR", "Add a new dmedia file store. Create it in the `PARENT_DIR` directory (eg. /home/username).") | ||
275 | 250 | dmedia_cli.add_dbus_action("RemoveFileStore", "PARENT_DIR", "Remove an existing dmedia file store.") | ||
276 | 251 | #dmedia_cli.add_dbus_action("GetDoc", "DOC_ID", "Get a document from CouchDB. `DOC_ID` is the _id of the document.") | ||
277 | 252 | #dmedia_cli.add_dbus_action("Upload", "FILE_ID STORE_ID", "Upload the file with id `FILE_ID` to the remote store with id `STORE_ID`.") | ||
278 | 253 | #dmedia_cli.add_dbus_action("Download", "FILE_ID STORE_ID", "Download the file with id `FILE_ID` from the remote store with id `STORE_ID`.") | ||
279 | 254 | #dmedia_cli.add_dbus_action("ListTransfers", "", "List active uploads and downloads.") | ||
280 | 255 | dmedia_cli.add_dbus_action("GetEnv", "", "Display dmedia env as JSON.") | ||
281 | 256 | #dmedia_cli.add_dbus_action("GetAuthURL", "", "Get URL with basic auth user and password.") | ||
282 | 257 | #dmedia_cli.add_dbus_action("HasApp", "", "") | ||
283 | 258 | |||
284 | 259 | if __name__ == '__main__': | ||
285 | 260 | dmedia_cli.main(*argv[1:]) | ||
424 | 261 | 177 | ||
425 | === modified file 'dmedia-gtk' | |||
426 | --- dmedia-gtk 2012-01-04 10:10:10 +0000 | |||
427 | +++ dmedia-gtk 2012-01-17 04:20:28 +0000 | |||
428 | @@ -157,7 +157,7 @@ | |||
429 | 157 | splash = 'splash.html' | 157 | splash = 'splash.html' |
430 | 158 | page = 'index.html' | 158 | page = 'index.html' |
431 | 159 | title = 'Dmedia' | 159 | title = 'Dmedia' |
433 | 160 | proxy_bus = 'org.freedesktop.DMedia' | 160 | proxy_bus = dmedia.BUS |
434 | 161 | 161 | ||
435 | 162 | signals = { | 162 | signals = { |
436 | 163 | 'create_project': ['title'], | 163 | 'create_project': ['title'], |
437 | 164 | 164 | ||
438 | === modified file 'dmedia-service' | |||
439 | --- dmedia-service 2012-01-03 02:31:47 +0000 | |||
440 | +++ dmedia-service 2012-01-17 04:20:28 +0000 | |||
441 | @@ -1,4 +1,4 @@ | |||
443 | 1 | #!/usr/bin/python | 1 | #!/usr/bin/python3 |
444 | 2 | 2 | ||
445 | 3 | # dmedia: distributed media library | 3 | # dmedia: distributed media library |
446 | 4 | # Copyright (C) 2011 Novacut Inc | 4 | # Copyright (C) 2011 Novacut Inc |
447 | @@ -22,159 +22,168 @@ | |||
448 | 22 | # Jason Gerard DeRose <jderose@novacut.com> | 22 | # Jason Gerard DeRose <jderose@novacut.com> |
449 | 23 | 23 | ||
450 | 24 | """ | 24 | """ |
455 | 25 | Hacky shim till it's possible to implement DBus servers in PyGI. | 25 | Dmedia DBus service on org.freedesktop.Dmedia. |
452 | 26 | |||
453 | 27 | Note that because this script must be Python2, it can't import anything from the | ||
454 | 28 | `dmedia` package as that will only be installed under Python3. | ||
456 | 29 | """ | 26 | """ |
457 | 30 | 27 | ||
462 | 31 | import argparse | 28 | import time |
463 | 32 | import os | 29 | start_time = time.time() |
464 | 33 | from os import path | 30 | |
465 | 34 | from subprocess import Popen | 31 | import optparse |
466 | 35 | import logging | 32 | import logging |
467 | 36 | import time | ||
468 | 37 | import sys | ||
469 | 38 | import json | 33 | import json |
470 | 34 | from os import path | ||
471 | 39 | 35 | ||
472 | 40 | import xdg.BaseDirectory | ||
473 | 41 | import dbus | 36 | import dbus |
474 | 42 | import dbus.service | 37 | import dbus.service |
475 | 43 | from dbus.mainloop.glib import DBusGMainLoop | 38 | from dbus.mainloop.glib import DBusGMainLoop |
481 | 44 | from dc3lib.microfiber import Database, NotFound | 39 | from microfiber import Database, NotFound |
482 | 45 | 40 | from gi.repository import GObject | |
483 | 46 | 41 | ||
484 | 47 | __version__ = '12.01.0' | 42 | import dmedia |
485 | 48 | BUS = 'org.freedesktop.DMedia' | 43 | from dmedia.core import Core, start_file_server |
486 | 44 | from dmedia.service.dbus import UDisks | ||
487 | 45 | from dmedia.service.avahi import Avahi | ||
488 | 46 | |||
489 | 47 | |||
490 | 48 | BUS = dmedia.BUS | ||
491 | 49 | IFACE = BUS | 49 | IFACE = BUS |
492 | 50 | log = logging.getLogger() | 50 | log = logging.getLogger() |
493 | 51 | service3 = path.join(path.dirname(path.abspath(__file__)), 'dmedia-service3') | ||
494 | 52 | assert path.isfile(service3) | ||
495 | 53 | 51 | ||
496 | 52 | GObject.threads_init() | ||
497 | 54 | DBusGMainLoop(set_as_default=True) | 53 | DBusGMainLoop(set_as_default=True) |
498 | 55 | session = dbus.SessionBus() | 54 | session = dbus.SessionBus() |
499 | 56 | 55 | ||
500 | 57 | 56 | ||
557 | 58 | def configure_logging(): | 57 | def dumps(obj): |
558 | 59 | format = [ | 58 | return json.dumps(obj, sort_keys=True, separators=(',', ': '), indent=4) |
559 | 60 | '%(levelname)s', | 59 | |
560 | 61 | '%(message)s', | 60 | |
561 | 62 | ] | 61 | class Service(dbus.service.Object): |
562 | 63 | script = path.abspath(sys.argv[0]) | 62 | httpd = None |
563 | 64 | namespace = path.basename(script) | 63 | avahi = None |
564 | 65 | cache = path.join(xdg.BaseDirectory.xdg_cache_home, 'dmedia') | 64 | |
565 | 66 | if not path.exists(cache): | 65 | def __init__(self, bus, env_s): |
566 | 67 | os.makedirs(cache) | 66 | self.bus = bus |
567 | 68 | filename = path.join(cache, namespace + '.log') | 67 | self.env_s = env_s |
568 | 69 | if path.exists(filename): | 68 | self.mainloop = GObject.MainLoop() |
569 | 70 | os.rename(filename, filename + '.previous') | 69 | log.info('DBus: binding to %r', bus) |
570 | 71 | logging.basicConfig( | 70 | super().__init__(session, object_path='/') |
571 | 72 | filename=filename, | 71 | self.busname = dbus.service.BusName(bus, session) |
572 | 73 | filemode='w', | 72 | |
573 | 74 | level=logging.DEBUG, | 73 | def start_core(self): |
574 | 75 | format='\t'.join(format), | 74 | if self.env_s is None: |
575 | 76 | ) | 75 | self.dc3 = session.get_object('org.freedesktop.DC3', '/') |
576 | 77 | logging.info('script: %r', script) | 76 | env = json.loads(self.dc3.GetEnv()) |
577 | 78 | logging.info('dmedia.__file__: %r', __file__) | 77 | else: |
578 | 79 | logging.info('dmedia.__version__: %r', __version__) | 78 | env = json.loads(self.env_s) |
579 | 80 | 79 | self.udisks = UDisks() | |
580 | 81 | 80 | self.core = Core(env, self.udisks.get_parentdir_info) | |
581 | 82 | class DMedia(dbus.service.Object): | 81 | self.env_s = dumps(self.core.env) |
582 | 83 | def __init__(self, bus, mainloop): | 82 | if len(self.core.stores) == 0: |
583 | 84 | self._killed = False | 83 | self.core.add_filestore('/home') |
584 | 85 | self._bus = bus | 84 | self.udisks.connect('store_added', self.on_store_added) |
585 | 86 | self._mainloop = mainloop | 85 | self.udisks.connect('store_removed', self.on_store_removed) |
586 | 87 | 86 | self.udisks.monitor() | |
587 | 88 | log.info('Binding to %r', bus) | 87 | |
588 | 89 | super(DMedia, self).__init__(session, object_path='/') | 88 | def start_httpd(self): |
589 | 90 | self._busname = dbus.service.BusName(bus, session) | 89 | (self.httpd, self.port) = start_file_server(self.core.env) |
590 | 91 | 90 | self.avahi = Avahi(self.core.env, self.port) | |
591 | 92 | self._dc3 = session.get_object('org.freedesktop.DC3', '/') | 91 | self.avahi.run() |
592 | 93 | self._machine_id = None | 92 | |
593 | 94 | env = self._get_env() | 93 | def run(self): |
594 | 95 | 94 | self.start_core() | |
595 | 96 | log.info('Starting %r', service3) | 95 | self.start_httpd() |
596 | 97 | self._child = Popen([service3, '--bus', bus]) | 96 | self.mainloop.run() |
597 | 98 | db = Database('dmedia-0', env) | 97 | |
598 | 99 | while True: | 98 | def kill(self): |
599 | 100 | try: | 99 | if self.avahi is not None: |
600 | 101 | time.sleep(0.1) | 100 | self.avahi.free() |
601 | 102 | doc = db.get('_local/dmedia') | 101 | if self.httpd is not None: |
602 | 103 | self._machine_id = doc['machine_id'] | 102 | self.httpd.terminate() |
603 | 104 | log.info('machine_id = %r', self._machine_id) | 103 | self.httpd.join() |
604 | 105 | break | 104 | self.mainloop.quit() |
605 | 106 | except NotFound: | 105 | |
606 | 107 | pass | 106 | def on_store_added(self, udisks, obj, parentdir, partition, drive): |
607 | 108 | 107 | log.info('UDisks store_added: %r', parentdir) | |
608 | 109 | def _get_env(self): | 108 | try: |
609 | 110 | log.info('Calling DC3.GetEnv()') | 109 | self.AddFileStore(parentdir) |
610 | 111 | env = json.loads(self._dc3.GetEnv()) | 110 | except Exception: |
611 | 112 | env['machine_id'] = self._machine_id | 111 | log.exception('Could not add FileStore %r', parentdir) |
612 | 113 | return env | 112 | |
613 | 113 | def on_store_removed(self, udisks, obj, parentdir): | ||
614 | 114 | log.info('UDisks store_removed: %r', parentdir) | ||
615 | 115 | try: | ||
616 | 116 | self.RemoveFileStore(parentdir) | ||
617 | 117 | except Exception: | ||
618 | 118 | log.exception('Could not remove FileStore %r', parentdir) | ||
619 | 114 | 119 | ||
620 | 115 | @dbus.service.method(IFACE, in_signature='', out_signature='s') | 120 | @dbus.service.method(IFACE, in_signature='', out_signature='s') |
621 | 116 | def Version(self): | 121 | def Version(self): |
622 | 117 | """ | 122 | """ |
623 | 118 | Return dmedia version. | 123 | Return dmedia version. |
624 | 119 | """ | 124 | """ |
626 | 120 | return __version__ | 125 | return dmedia.__version__ |
627 | 121 | 126 | ||
629 | 122 | @dbus.service.method(IFACE, in_signature='', out_signature='') | 127 | @dbus.service.method(IFACE, in_signature='', out_signature='i') |
630 | 123 | def Kill(self): | 128 | def Kill(self): |
631 | 124 | """ | 129 | """ |
632 | 125 | Kill the `dmedia-service` process. | 130 | Kill the `dmedia-service` process. |
633 | 126 | """ | 131 | """ |
645 | 127 | if self._killed: | 132 | self.kill() |
646 | 128 | return | 133 | return int(time.time() - start_time) |
636 | 129 | self._killed = True | ||
637 | 130 | log.info('Killing dmedia core service on %r', self._bus) | ||
638 | 131 | self.FwdKill() | ||
639 | 132 | self._child.wait() | ||
640 | 133 | self._mainloop.quit() | ||
641 | 134 | |||
642 | 135 | @dbus.service.signal(IFACE, signature='') | ||
643 | 136 | def FwdKill(self): | ||
644 | 137 | pass | ||
647 | 138 | 134 | ||
648 | 139 | @dbus.service.method(IFACE, in_signature='', out_signature='s') | 135 | @dbus.service.method(IFACE, in_signature='', out_signature='s') |
649 | 140 | def GetEnv(self): | 136 | def GetEnv(self): |
650 | 141 | """ | 137 | """ |
651 | 142 | Return dmedia env as JSON string. | 138 | Return dmedia env as JSON string. |
652 | 143 | """ | 139 | """ |
656 | 144 | return json.dumps(self._get_env()) | 140 | return self.env_s |
657 | 145 | 141 | ||
658 | 146 | @dbus.service.method(IFACE, in_signature='s', out_signature='') | 142 | @dbus.service.method(IFACE, in_signature='', out_signature='s') |
659 | 143 | def LocalDmedia(self): | ||
660 | 144 | """ | ||
661 | 145 | Return the _local/dmedia doc. | ||
662 | 146 | """ | ||
663 | 147 | return dumps(self.core.db.get('_local/dmedia')) | ||
664 | 148 | |||
665 | 149 | @dbus.service.method(IFACE, in_signature='', out_signature='s') | ||
666 | 150 | def LocalPeers(self): | ||
667 | 151 | """ | ||
668 | 152 | Return the _local/peers doc. | ||
669 | 153 | """ | ||
670 | 154 | return dumps(self.core.db.get('_local/peers')) | ||
671 | 155 | |||
672 | 156 | @dbus.service.method(IFACE, in_signature='s', out_signature='s') | ||
673 | 147 | def AddFileStore(self, parentdir): | 157 | def AddFileStore(self, parentdir): |
681 | 148 | self.FwdAddFileStore(parentdir) | 158 | fs = self.core.add_filestore(parentdir) |
682 | 149 | 159 | return self.LocalDmedia() | |
683 | 150 | @dbus.service.signal(IFACE, signature='s') | 160 | |
684 | 151 | def FwdAddFileStore(self, parentdir): | 161 | @dbus.service.method(IFACE, in_signature='s', out_signature='s') |
678 | 152 | pass | ||
679 | 153 | |||
680 | 154 | @dbus.service.method(IFACE, in_signature='s', out_signature='') | ||
685 | 155 | def RemoveFileStore(self, parentdir): | 162 | def RemoveFileStore(self, parentdir): |
711 | 156 | self.FwdRemoveFileStore(parentdir) | 163 | fs = self.core.remove_filestore(parentdir) |
712 | 157 | 164 | return self.LocalDmedia() | |
713 | 158 | @dbus.service.signal(IFACE, signature='s') | 165 | |
714 | 159 | def FwdRemoveFileStore(self, parentdir): | 166 | @dbus.service.method(IFACE, in_signature='s', out_signature='s') |
715 | 160 | pass | 167 | def Resolve(self, _id): |
716 | 161 | 168 | return self.core.resolve(_id) | |
717 | 162 | 169 | ||
718 | 163 | parser = argparse.ArgumentParser( | 170 | |
719 | 164 | description='DBus service @{}'.format(BUS), | 171 | parser = optparse.OptionParser( |
720 | 165 | ) | 172 | version=dmedia.__version__, |
721 | 166 | parser.add_argument('--version', action='version', version=__version__) | 173 | ) |
722 | 167 | parser.add_argument('--bus', | 174 | parser.add_option('--env', |
723 | 168 | default=BUS, | 175 | help='JSON-encoded CouchDB environment (for unit testing)', |
724 | 169 | help='DBus bus name; default is %(default)r', | 176 | ) |
725 | 170 | ) | 177 | parser.add_option('--bus', |
726 | 171 | args = parser.parse_args() | 178 | help='DBus bus name; default is {!r}'.format(BUS), |
727 | 172 | configure_logging() | 179 | default=BUS |
728 | 173 | 180 | ) | |
729 | 174 | from gi.repository import GObject | 181 | (options, args) = parser.parse_args() |
730 | 175 | GObject.threads_init() | 182 | |
731 | 176 | 183 | ||
732 | 177 | mainloop = GObject.MainLoop() | 184 | dmedia.configure_logging() |
733 | 178 | dmedia = DMedia(args.bus, mainloop) | 185 | service = Service(options.bus, options.env) |
734 | 179 | mainloop.run() | 186 | try: |
735 | 180 | dmedia.Kill() | 187 | service.run() |
736 | 188 | finally: | ||
737 | 189 | service.kill() | ||
738 | 181 | 190 | ||
739 | === removed file 'dmedia-service3' | |||
740 | --- dmedia-service3 2011-12-27 10:02:37 +0000 | |||
741 | +++ dmedia-service3 1970-01-01 00:00:00 +0000 | |||
742 | @@ -1,181 +0,0 @@ | |||
743 | 1 | #!/usr/bin/python3 | ||
744 | 2 | |||
745 | 3 | # dmedia: distributed media library | ||
746 | 4 | # Copyright (C) 2011 Novacut Inc | ||
747 | 5 | # | ||
748 | 6 | # This file is part of `dmedia`. | ||
749 | 7 | # | ||
750 | 8 | # `dmedia` is free software: you can redistribute it and/or modify it under | ||
751 | 9 | # the terms of the GNU Affero General Public License as published by the Free | ||
752 | 10 | # Software Foundation, either version 3 of the License, or (at your option) any | ||
753 | 11 | # later version. | ||
754 | 12 | # | ||
755 | 13 | # `dmedia` is distributed in the hope that it will be useful, but WITHOUT ANY | ||
756 | 14 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | ||
757 | 15 | # A PARTICULAR PURPOSE. See the GNU Affero General Public License for more | ||
758 | 16 | # details. | ||
759 | 17 | # | ||
760 | 18 | # You should have received a copy of the GNU Affero General Public License along | ||
761 | 19 | # with `dmedia`. If not, see <http://www.gnu.org/licenses/>. | ||
762 | 20 | # | ||
763 | 21 | # Authors: | ||
764 | 22 | # Jason Gerard DeRose <jderose@novacut.com> | ||
765 | 23 | |||
766 | 24 | """ | ||
767 | 25 | The real Python3 dmedia DBus service (well, almost). | ||
768 | 26 | """ | ||
769 | 27 | |||
770 | 28 | import sys | ||
771 | 29 | import json | ||
772 | 30 | from os import path | ||
773 | 31 | import optparse | ||
774 | 32 | import logging | ||
775 | 33 | |||
776 | 34 | from gi.repository import GObject | ||
777 | 35 | from microfiber import NotFound | ||
778 | 36 | |||
779 | 37 | import dmedia | ||
780 | 38 | from dmedia.service import dbus | ||
781 | 39 | from dmedia.service.dbus import session, UDisks | ||
782 | 40 | from dmedia.core import Core, start_file_server | ||
783 | 41 | |||
784 | 42 | GObject.threads_init() | ||
785 | 43 | log = logging.getLogger() | ||
786 | 44 | |||
787 | 45 | |||
788 | 46 | class DMedia: | ||
789 | 47 | def __init__(self, bus): | ||
790 | 48 | self.mainloop = GObject.MainLoop() | ||
791 | 49 | dc3 = session.get('org.freedesktop.DC3', '/') | ||
792 | 50 | env = json.loads(dc3.GetEnv()) | ||
793 | 51 | self.udisks = UDisks() | ||
794 | 52 | self.core = Core(env, self.udisks.get_parentdir_info) | ||
795 | 53 | if len(self.core.stores) == 0: | ||
796 | 54 | self.core.add_filestore('/home') | ||
797 | 55 | self._fwd = { | ||
798 | 56 | 'AddFileStore': self.AddFileStore, | ||
799 | 57 | 'RemoveFileStore': self.RemoveFileStore, | ||
800 | 58 | 'Kill': self.Kill, | ||
801 | 59 | } | ||
802 | 60 | if bus: | ||
803 | 61 | log.info('Listing to forwards from %r', bus) | ||
804 | 62 | self.proxy = session.get(bus, '/', 'org.freedesktop.DMedia') | ||
805 | 63 | self.proxy.connect('g-signal', self.on_g_signal) | ||
806 | 64 | self.avahi = dbus.system.get( | ||
807 | 65 | 'org.freedesktop.Avahi', | ||
808 | 66 | '/', | ||
809 | 67 | 'org.freedesktop.Avahi.Server' | ||
810 | 68 | ) | ||
811 | 69 | try: | ||
812 | 70 | self._peers = self.core.db.get('_local/peers') | ||
813 | 71 | except NotFound: | ||
814 | 72 | self._peers = {'_id': '_local/peers'} | ||
815 | 73 | self._peers['peers'] = {} | ||
816 | 74 | self.core.db.save(self._peers) | ||
817 | 75 | self.udisks.connect('store_added', self.on_store_added) | ||
818 | 76 | self.udisks.connect('store_removed', self.on_store_removed) | ||
819 | 77 | self.udisks.monitor() | ||
820 | 78 | |||
821 | 79 | def run(self): | ||
822 | 80 | (self.httpd, self.port) = start_file_server(self.core.env) | ||
823 | 81 | self.group = dbus.system.get( | ||
824 | 82 | 'org.freedesktop.Avahi', | ||
825 | 83 | self.avahi.EntryGroupNew(), | ||
826 | 84 | 'org.freedesktop.Avahi.EntryGroup' | ||
827 | 85 | ) | ||
828 | 86 | self.group.AddService('(iiussssqaay)', | ||
829 | 87 | -1, # Interface | ||
830 | 88 | 0, # Protocol -1 = both, 0 = ipv4, 1 = ipv6 | ||
831 | 89 | 0, # Flags | ||
832 | 90 | self.core.machine_id, | ||
833 | 91 | '_dmedia._tcp', | ||
834 | 92 | '', # Domain, default to .local | ||
835 | 93 | '', # Host, default to localhost | ||
836 | 94 | self.port, # Port | ||
837 | 95 | None # TXT record | ||
838 | 96 | ) | ||
839 | 97 | self.group.Commit() | ||
840 | 98 | browser_path = self.avahi.ServiceBrowserNew('(iissu)', | ||
841 | 99 | -1, # Interface | ||
842 | 100 | 0, # Protocol -1 = both, 0 = ipv4, 1 = ipv6 | ||
843 | 101 | '_dmedia._tcp', | ||
844 | 102 | 'local', | ||
845 | 103 | 0 # Flags | ||
846 | 104 | ) | ||
847 | 105 | self.browser = dbus.system.get( | ||
848 | 106 | 'org.freedesktop.Avahi', | ||
849 | 107 | browser_path, | ||
850 | 108 | 'org.freedesktop.Avahi.ServiceBrowser' | ||
851 | 109 | ) | ||
852 | 110 | self.browser.connect('g-signal', self.on_browser_g_signal) | ||
853 | 111 | self.mainloop.run() | ||
854 | 112 | |||
855 | 113 | def on_store_added(self, udisks, obj, parentdir, partition, drive): | ||
856 | 114 | log.info('UDisks store_added: %r', parentdir) | ||
857 | 115 | try: | ||
858 | 116 | self.AddFileStore(parentdir) | ||
859 | 117 | except Exception: | ||
860 | 118 | log.exception('Could not add FileStore %r', parentdir) | ||
861 | 119 | |||
862 | 120 | def on_store_removed(self, udisks, obj, parentdir): | ||
863 | 121 | log.info('UDisks store_removed: %r', parentdir) | ||
864 | 122 | try: | ||
865 | 123 | self.RemoveFileStore(parentdir) | ||
866 | 124 | except Exception: | ||
867 | 125 | log.exception('Could not remove FileStore %r', parentdir) | ||
868 | 126 | |||
869 | 127 | def on_g_signal(self, proxy, sender, signal, params): | ||
870 | 128 | if signal.startswith('Fwd'): | ||
871 | 129 | name = signal[3:] | ||
872 | 130 | args = params.unpack() | ||
873 | 131 | try: | ||
874 | 132 | self._fwd[name](*args) | ||
875 | 133 | except KeyError: | ||
876 | 134 | pass | ||
877 | 135 | |||
878 | 136 | def on_browser_g_signal(self, proxy, sender, signal, params): | ||
879 | 137 | if signal == 'ItemNew': | ||
880 | 138 | (interface, protocol, name, _type, domain, flags) = params.unpack() | ||
881 | 139 | if name != self.core.machine_id: # Ignore what we publish ourselves | ||
882 | 140 | (ip, port) = self.avahi.ResolveService('(iisssiu)', | ||
883 | 141 | interface, protocol, name, _type, domain, -1, 0 | ||
884 | 142 | )[7:9] | ||
885 | 143 | url = 'http://{}:{}/'.format(ip, port) | ||
886 | 144 | log.info('New peer %r at %r', name, url) | ||
887 | 145 | self._peers['peers'][name] = url | ||
888 | 146 | self.core.db.save(self._peers) | ||
889 | 147 | elif signal == 'ItemRemove': | ||
890 | 148 | (interface, protocol, name, _type, domain, flags) = params.unpack() | ||
891 | 149 | log.info('Removing peer %r', name) | ||
892 | 150 | try: | ||
893 | 151 | del self._peers['peers'][name] | ||
894 | 152 | self.core.db.save(self._peers) | ||
895 | 153 | except KeyError: | ||
896 | 154 | pass | ||
897 | 155 | |||
898 | 156 | def AddFileStore(self, parentdir): | ||
899 | 157 | self.core.add_filestore(path.abspath(parentdir)) | ||
900 | 158 | |||
901 | 159 | def RemoveFileStore(self, parentdir): | ||
902 | 160 | self.core.remove_filestore(path.abspath(parentdir)) | ||
903 | 161 | |||
904 | 162 | def Kill(self): | ||
905 | 163 | self.group.Reset() | ||
906 | 164 | self.httpd.terminate() | ||
907 | 165 | self.httpd.join() | ||
908 | 166 | self.mainloop.quit() | ||
909 | 167 | |||
910 | 168 | |||
911 | 169 | parser = optparse.OptionParser( | ||
912 | 170 | usage='Usage: %prog FILE', | ||
913 | 171 | version=dmedia.__version__, | ||
914 | 172 | ) | ||
915 | 173 | parser.add_option('--bus', | ||
916 | 174 | help='If provided, expect Python2 shim on this DBus bus', | ||
917 | 175 | ) | ||
918 | 176 | (options, args) = parser.parse_args() | ||
919 | 177 | |||
920 | 178 | dmedia.configure_logging() | ||
921 | 179 | service = DMedia(options.bus) | ||
922 | 180 | service.run() | ||
923 | 181 | service.Kill() | ||
924 | 182 | 0 | ||
925 | === modified file 'dmedia/__init__.py' | |||
926 | --- dmedia/__init__.py 2011-12-27 07:21:46 +0000 | |||
927 | +++ dmedia/__init__.py 2012-01-17 04:20:28 +0000 | |||
928 | @@ -27,6 +27,7 @@ | |||
929 | 27 | """ | 27 | """ |
930 | 28 | 28 | ||
931 | 29 | __version__ = '12.01.0' | 29 | __version__ = '12.01.0' |
932 | 30 | BUS = 'org.freedesktop.Dmedia' | ||
933 | 30 | 31 | ||
934 | 31 | 32 | ||
935 | 32 | def configure_logging(): | 33 | def configure_logging(): |
936 | 33 | 34 | ||
937 | === modified file 'dmedia/constants.py' | |||
938 | --- dmedia/constants.py 2012-01-03 02:12:57 +0000 | |||
939 | +++ dmedia/constants.py 2012-01-17 04:20:28 +0000 | |||
940 | @@ -45,10 +45,8 @@ | |||
941 | 45 | 45 | ||
942 | 46 | 46 | ||
943 | 47 | # D-Bus releated: | 47 | # D-Bus releated: |
945 | 48 | BUS = 'org.freedesktop.DMedia' | 48 | BUS = 'org.freedesktop.Dmedia' |
946 | 49 | IFACE = BUS | 49 | IFACE = BUS |
947 | 50 | IMPORT_BUS = 'org.freedesktop.DMediaImporter' | ||
948 | 51 | IMPORT_IFACE = IMPORT_BUS | ||
949 | 52 | DC_BUS = 'org.desktopcouch.CouchDB' | 50 | DC_BUS = 'org.desktopcouch.CouchDB' |
950 | 53 | DC_INTERFACE = DC_BUS | 51 | DC_INTERFACE = DC_BUS |
951 | 54 | 52 | ||
952 | 55 | 53 | ||
953 | === modified file 'dmedia/core.py' | |||
954 | --- dmedia/core.py 2012-01-02 23:49:23 +0000 | |||
955 | +++ dmedia/core.py 2012-01-17 04:20:28 +0000 | |||
956 | @@ -39,6 +39,7 @@ | |||
957 | 39 | from microfiber import Database, NotFound, random_id2 | 39 | from microfiber import Database, NotFound, random_id2 |
958 | 40 | from filestore import FileStore, check_root_hash, check_id | 40 | from filestore import FileStore, check_root_hash, check_id |
959 | 41 | 41 | ||
960 | 42 | import dmedia | ||
961 | 42 | from dmedia import schema | 43 | from dmedia import schema |
962 | 43 | from dmedia.local import LocalStores | 44 | from dmedia.local import LocalStores |
963 | 44 | from dmedia.views import init_views | 45 | from dmedia.views import init_views |
964 | @@ -133,6 +134,7 @@ | |||
965 | 133 | self.db.save(machine) | 134 | self.db.save(machine) |
966 | 134 | self.machine_id = self.local['machine_id'] | 135 | self.machine_id = self.local['machine_id'] |
967 | 135 | self.env['machine_id'] = self.machine_id | 136 | self.env['machine_id'] = self.machine_id |
968 | 137 | self.env['version'] = dmedia.__version__ | ||
969 | 136 | log.info('machine_id = %r', self.machine_id) | 138 | log.info('machine_id = %r', self.machine_id) |
970 | 137 | 139 | ||
971 | 138 | def _init_stores(self): | 140 | def _init_stores(self): |
972 | @@ -207,3 +209,7 @@ | |||
973 | 207 | assert set(self.local['stores']) == set(self.stores.parentdirs) | 209 | assert set(self.local['stores']) == set(self.stores.parentdirs) |
974 | 208 | assert set(s['id'] for s in self.local['stores'].values()) == set(self.stores.ids) | 210 | assert set(s['id'] for s in self.local['stores'].values()) == set(self.stores.ids) |
975 | 209 | 211 | ||
976 | 212 | def resolve(self, _id): | ||
977 | 213 | doc = self.db.get(_id) | ||
978 | 214 | fs = self.stores.choose_local_store(doc) | ||
979 | 215 | return fs.stat(_id).name | ||
980 | 210 | 216 | ||
981 | === modified file 'dmedia/service/api.py' | |||
982 | --- dmedia/service/api.py 2011-10-24 21:36:05 +0000 | |||
983 | +++ dmedia/service/api.py 2012-01-17 04:20:28 +0000 | |||
984 | @@ -30,10 +30,10 @@ | |||
985 | 30 | 30 | ||
986 | 31 | class DMedia: | 31 | class DMedia: |
987 | 32 | """ | 32 | """ |
989 | 33 | Talk to "org.freedesktop.DMedia". | 33 | Talk to "org.freedesktop.Dmedia". |
990 | 34 | """ | 34 | """ |
991 | 35 | 35 | ||
993 | 36 | def __init__(self, bus='org.freedesktop.DMedia'): | 36 | def __init__(self, bus='org.freedesktop.Dmedia'): |
994 | 37 | self.bus = bus | 37 | self.bus = bus |
995 | 38 | self._proxy = None | 38 | self._proxy = None |
996 | 39 | 39 | ||
997 | 40 | 40 | ||
998 | === added file 'dmedia/service/avahi.py' | |||
999 | --- dmedia/service/avahi.py 1970-01-01 00:00:00 +0000 | |||
1000 | +++ dmedia/service/avahi.py 2012-01-17 04:20:28 +0000 | |||
1001 | @@ -0,0 +1,116 @@ | |||
1002 | 1 | # dmedia: distributed media library | ||
1003 | 2 | # Copyright (C) 2011 Novacut Inc | ||
1004 | 3 | # | ||
1005 | 4 | # This file is part of `dmedia`. | ||
1006 | 5 | # | ||
1007 | 6 | # `dmedia` is free software: you can redistribute it and/or modify it under the | ||
1008 | 7 | # terms of the GNU Affero General Public License as published by the Free | ||
1009 | 8 | # Software Foundation, either version 3 of the License, or (at your option) any | ||
1010 | 9 | # later version. | ||
1011 | 10 | # | ||
1012 | 11 | # `dmedia` is distributed in the hope that it will be useful, but WITHOUT ANY | ||
1013 | 12 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | ||
1014 | 13 | # A PARTICULAR PURPOSE. See the GNU Affero General Public License for more | ||
1015 | 14 | # details. | ||
1016 | 15 | # | ||
1017 | 16 | # You should have received a copy of the GNU Affero General Public License along | ||
1018 | 17 | # with `dmedia`. If not, see <http://www.gnu.org/licenses/>. | ||
1019 | 18 | # | ||
1020 | 19 | # Authors: | ||
1021 | 20 | # Jason Gerard DeRose <jderose@novacut.com> | ||
1022 | 21 | |||
1023 | 22 | """ | ||
1024 | 23 | Advertise Dmedia HTTP server over Avahi, discover other peers. | ||
1025 | 24 | """ | ||
1026 | 25 | |||
1027 | 26 | import logging | ||
1028 | 27 | |||
1029 | 28 | from microfiber import Database, NotFound | ||
1030 | 29 | |||
1031 | 30 | from dmedia.service.dbus import system | ||
1032 | 31 | |||
1033 | 32 | |||
1034 | 33 | PEERS = '_local/peers' | ||
1035 | 34 | log = logging.getLogger() | ||
1036 | 35 | |||
1037 | 36 | |||
1038 | 37 | class Avahi: | ||
1039 | 38 | group = None | ||
1040 | 39 | |||
1041 | 40 | def __init__(self, env, port): | ||
1042 | 41 | self.avahi = system.get( | ||
1043 | 42 | 'org.freedesktop.Avahi', | ||
1044 | 43 | '/', | ||
1045 | 44 | 'org.freedesktop.Avahi.Server' | ||
1046 | 45 | ) | ||
1047 | 46 | self.db = Database('dmedia-0', env) | ||
1048 | 47 | self.machine_id = env['machine_id'] | ||
1049 | 48 | self.port = port | ||
1050 | 49 | |||
1051 | 50 | def __del__(self): | ||
1052 | 51 | self.free() | ||
1053 | 52 | |||
1054 | 53 | def run(self): | ||
1055 | 54 | try: | ||
1056 | 55 | self.peers = self.db.get(PEERS) | ||
1057 | 56 | if self.peers.get('peers') != {}: | ||
1058 | 57 | self.peers['peers'] = {} | ||
1059 | 58 | self.db.save(self.peers) | ||
1060 | 59 | except NotFound: | ||
1061 | 60 | self.peers = {'_id': PEERS, 'peers': {}} | ||
1062 | 61 | self.db.save(self.peers) | ||
1063 | 62 | self.group = system.get( | ||
1064 | 63 | 'org.freedesktop.Avahi', | ||
1065 | 64 | self.avahi.EntryGroupNew(), | ||
1066 | 65 | 'org.freedesktop.Avahi.EntryGroup' | ||
1067 | 66 | ) | ||
1068 | 67 | log.info('Avahi: advertising %r on port %r', self.machine_id, self.port) | ||
1069 | 68 | self.group.AddService('(iiussssqaay)', | ||
1070 | 69 | -1, # Interface | ||
1071 | 70 | 0, # Protocol -1 = both, 0 = ipv4, 1 = ipv6 | ||
1072 | 71 | 0, # Flags | ||
1073 | 72 | self.machine_id, | ||
1074 | 73 | '_dmedia._tcp', | ||
1075 | 74 | '', # Domain, default to .local | ||
1076 | 75 | '', # Host, default to localhost | ||
1077 | 76 | self.port, # Port | ||
1078 | 77 | None # TXT record | ||
1079 | 78 | ) | ||
1080 | 79 | self.group.Commit() | ||
1081 | 80 | browser_path = self.avahi.ServiceBrowserNew('(iissu)', | ||
1082 | 81 | -1, # Interface | ||
1083 | 82 | 0, # Protocol -1 = both, 0 = ipv4, 1 = ipv6 | ||
1084 | 83 | '_dmedia._tcp', | ||
1085 | 84 | 'local', | ||
1086 | 85 | 0 # Flags | ||
1087 | 86 | ) | ||
1088 | 87 | self.browser = system.get( | ||
1089 | 88 | 'org.freedesktop.Avahi', | ||
1090 | 89 | browser_path, | ||
1091 | 90 | 'org.freedesktop.Avahi.ServiceBrowser' | ||
1092 | 91 | ) | ||
1093 | 92 | self.browser.connect('g-signal', self.on_g_signal) | ||
1094 | 93 | |||
1095 | 94 | def free(self): | ||
1096 | 95 | if self.group is not None: | ||
1097 | 96 | self.group.Reset() | ||
1098 | 97 | |||
1099 | 98 | def on_g_signal(self, proxy, sender, signal, params): | ||
1100 | 99 | if signal == 'ItemNew': | ||
1101 | 100 | (interface, protocol, name, _type, domain, flags) = params.unpack() | ||
1102 | 101 | if name != self.machine_id: # Ignore what we publish ourselves | ||
1103 | 102 | (ip, port) = self.avahi.ResolveService('(iisssiu)', | ||
1104 | 103 | interface, protocol, name, _type, domain, -1, 0 | ||
1105 | 104 | )[7:9] | ||
1106 | 105 | url = 'http://{}:{}/'.format(ip, port) | ||
1107 | 106 | log.info('Avahi: new peer %r at %r', name, url) | ||
1108 | 107 | self.peers['peers'][name] = url | ||
1109 | 108 | self.db.save(self.peers) | ||
1110 | 109 | elif signal == 'ItemRemove': | ||
1111 | 110 | (interface, protocol, name, _type, domain, flags) = params.unpack() | ||
1112 | 111 | log.info('Avahi: removing peer %r', name) | ||
1113 | 112 | try: | ||
1114 | 113 | del self.peers['peers'][name] | ||
1115 | 114 | self.db.save(self.peers) | ||
1116 | 115 | except KeyError: | ||
1117 | 116 | pass | ||
1118 | 0 | 117 | ||
1119 | === modified file 'dmedia/transfers.py' | |||
1120 | --- dmedia/transfers.py 2011-09-22 10:20:44 +0000 | |||
1121 | +++ dmedia/transfers.py 2012-01-17 04:20:28 +0000 | |||
1122 | @@ -41,7 +41,7 @@ | |||
1123 | 41 | _uploaders = {} | 41 | _uploaders = {} |
1124 | 42 | _downloaders = {} | 42 | _downloaders = {} |
1125 | 43 | 43 | ||
1127 | 44 | # Note: should probably export each download on the org.freedesktop.DMedia bus | 44 | # Note: should probably export each download on the org.freedesktop.Dmedia bus |
1128 | 45 | # at the object path /downloads/FILE_ID | 45 | # at the object path /downloads/FILE_ID |
1129 | 46 | 46 | ||
1130 | 47 | def download_key(file_id, store_id): | 47 | def download_key(file_id, store_id): |
1131 | @@ -63,7 +63,7 @@ | |||
1132 | 63 | """ | 63 | """ |
1133 | 64 | return '/downloads/' + file_id | 64 | return '/downloads/' + file_id |
1134 | 65 | 65 | ||
1136 | 66 | # Note: should probably export each upload on the org.freedesktop.DMedia bus | 66 | # Note: should probably export each upload on the org.freedesktop.Dmedia bus |
1137 | 67 | # at the object path /uploads/FILE_ID/REMOTE_ID | 67 | # at the object path /uploads/FILE_ID/REMOTE_ID |
1138 | 68 | 68 | ||
1139 | 69 | def upload_key(file_id, store_id): | 69 | def upload_key(file_id, store_id): |
1140 | 70 | 70 | ||
1141 | === modified file 'dmedia/units.py' | |||
1142 | --- dmedia/units.py 2011-10-08 11:18:57 +0000 | |||
1143 | +++ dmedia/units.py 2012-01-17 04:20:28 +0000 | |||
1144 | @@ -68,3 +68,16 @@ | |||
1145 | 68 | return ( | 68 | return ( |
1146 | 69 | '{:.3g} {}'.format(s, BYTES10[i]) | 69 | '{:.3g} {}'.format(s, BYTES10[i]) |
1147 | 70 | ) | 70 | ) |
1148 | 71 | |||
1149 | 72 | |||
1150 | 73 | def minsec(seconds): | ||
1151 | 74 | """ | ||
1152 | 75 | Format *seconds* as a M:SS string with minutes and seconds. | ||
1153 | 76 | |||
1154 | 77 | For example: | ||
1155 | 78 | |||
1156 | 79 | >>> minsec(123) | ||
1157 | 80 | '2:03' | ||
1158 | 81 | |||
1159 | 82 | """ | ||
1160 | 83 | return '{:d}:{:02d}'.format(seconds // 60, seconds % 60) | ||
1161 | 71 | 84 | ||
1162 | === modified file 'setup.py' | |||
1163 | --- setup.py 2012-01-15 15:21:24 +0000 | |||
1164 | +++ setup.py 2012-01-17 04:20:28 +0000 | |||
1165 | @@ -159,12 +159,11 @@ | |||
1166 | 159 | ('lib/dmedia', | 159 | ('lib/dmedia', |
1167 | 160 | [ | 160 | [ |
1168 | 161 | 'dmedia-service', | 161 | 'dmedia-service', |
1169 | 162 | 'dmedia-service3', | ||
1170 | 163 | 'share/init-filestore', | 162 | 'share/init-filestore', |
1171 | 164 | ] | 163 | ] |
1172 | 165 | ), | 164 | ), |
1173 | 166 | ('share/dbus-1/services/', | 165 | ('share/dbus-1/services/', |
1175 | 167 | ['share/org.freedesktop.DMedia.service'] | 166 | ['share/org.freedesktop.Dmedia.service'] |
1176 | 168 | ), | 167 | ), |
1177 | 169 | ], | 168 | ], |
1178 | 170 | ) | 169 | ) |
1179 | 171 | 170 | ||
1180 | === renamed file 'share/org.freedesktop.DMedia.service' => 'share/org.freedesktop.Dmedia.service' | |||
1181 | --- share/org.freedesktop.DMedia.service 2011-04-10 11:02:23 +0000 | |||
1182 | +++ share/org.freedesktop.Dmedia.service 2012-01-17 04:20:28 +0000 | |||
1183 | @@ -1,3 +1,3 @@ | |||
1184 | 1 | [D-BUS Service] | 1 | [D-BUS Service] |
1186 | 2 | Name=org.freedesktop.DMedia | 2 | Name=org.freedesktop.Dmedia |
1187 | 3 | Exec=/usr/lib/dmedia/dmedia-service | 3 | Exec=/usr/lib/dmedia/dmedia-service |
1188 | 4 | 4 | ||
1189 | === added file 'test-cli.py' | |||
1190 | --- test-cli.py 1970-01-01 00:00:00 +0000 | |||
1191 | +++ test-cli.py 2012-01-17 04:20:28 +0000 | |||
1192 | @@ -0,0 +1,33 @@ | |||
1193 | 1 | #!/usr/bin/python3 | ||
1194 | 2 | |||
1195 | 3 | from usercouch.misc import TempCouch | ||
1196 | 4 | from microfiber import random_id | ||
1197 | 5 | import json | ||
1198 | 6 | import time | ||
1199 | 7 | from subprocess import Popen, check_output | ||
1200 | 8 | |||
1201 | 9 | bus = 'tmp' + random_id() + '.Dmedia' | ||
1202 | 10 | |||
1203 | 11 | def call(*args): | ||
1204 | 12 | cmd = ('./dmedia-cli', '--bus', bus) + args | ||
1205 | 13 | print(check_output(cmd).decode('utf-8')) | ||
1206 | 14 | |||
1207 | 15 | # Check that printing help doesn't connect to the DBus service: | ||
1208 | 16 | call() | ||
1209 | 17 | |||
1210 | 18 | # Start a tmp couchdb and the dbus service on a random bus name: | ||
1211 | 19 | tmpcouch = TempCouch() | ||
1212 | 20 | env = tmpcouch.bootstrap() | ||
1213 | 21 | cmd = ['./dmedia-service', '--bus', bus, '--env', json.dumps(env)] | ||
1214 | 22 | child = Popen(cmd) | ||
1215 | 23 | time.sleep(1) | ||
1216 | 24 | |||
1217 | 25 | try: | ||
1218 | 26 | call('Version') | ||
1219 | 27 | call('GetEnv') | ||
1220 | 28 | call('LocalDmedia') | ||
1221 | 29 | call('LocalPeers') | ||
1222 | 30 | call('RemoveFileStore', '/home/') | ||
1223 | 31 | call('AddFileStore', '/home/') | ||
1224 | 32 | finally: | ||
1225 | 33 | call('Kill') | ||
1226 | 0 | 34 | ||
1227 | === added file 'test-service.py' | |||
1228 | --- test-service.py 1970-01-01 00:00:00 +0000 | |||
1229 | +++ test-service.py 2012-01-17 04:20:28 +0000 | |||
1230 | @@ -0,0 +1,29 @@ | |||
1231 | 1 | #!/usr/bin/python3 | ||
1232 | 2 | |||
1233 | 3 | from usercouch.misc import TempCouch | ||
1234 | 4 | from microfiber import random_id | ||
1235 | 5 | import json | ||
1236 | 6 | import time | ||
1237 | 7 | from subprocess import Popen | ||
1238 | 8 | from dmedia.service.dbus import session | ||
1239 | 9 | |||
1240 | 10 | bus = 'tmp' + random_id() + '.Dmedia' | ||
1241 | 11 | tmpcouch = TempCouch() | ||
1242 | 12 | env = tmpcouch.bootstrap() | ||
1243 | 13 | |||
1244 | 14 | cmd = ['./dmedia-service', '--bus', bus, '--env', json.dumps(env)] | ||
1245 | 15 | |||
1246 | 16 | child = Popen(cmd) | ||
1247 | 17 | time.sleep(2) | ||
1248 | 18 | |||
1249 | 19 | proxy = session.get(bus, '/', 'org.freedesktop.Dmedia') | ||
1250 | 20 | try: | ||
1251 | 21 | print(proxy.Version()) | ||
1252 | 22 | print(proxy.GetEnv()) | ||
1253 | 23 | print(proxy.LocalDmedia()) | ||
1254 | 24 | print(proxy.LocalPeers()) | ||
1255 | 25 | print(proxy.RemoveFileStore('(s)', '/home')) | ||
1256 | 26 | print(proxy.AddFileStore('(s)', '/home')) | ||
1257 | 27 | finally: | ||
1258 | 28 | print(proxy.Kill()) | ||
1259 | 29 |