| 1 | #!/usr/bin/env python |
|---|
| 2 | |
|---|
| 3 | # Deejayd, a media player daemon |
|---|
| 4 | # Copyright (C) 2007-2009 Mickael Royer <mickael.royer@gmail.com> |
|---|
| 5 | # Alexandre Rossi <alexandre.rossi@gmail.com> |
|---|
| 6 | # |
|---|
| 7 | # This program is free software; you can redistribute it and/or modify |
|---|
| 8 | # it under the terms of the GNU General Public License as published by |
|---|
| 9 | # the Free Software Foundation; either version 2 of the License, or |
|---|
| 10 | # (at your option) any later version. |
|---|
| 11 | # |
|---|
| 12 | # This program is distributed in the hope that it will be useful, |
|---|
| 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 15 | # GNU General Public License for more details. |
|---|
| 16 | # |
|---|
| 17 | # You should have received a copy of the GNU General Public License along |
|---|
| 18 | # with this program; if not, write to the Free Software Foundation, Inc., |
|---|
| 19 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
|---|
| 20 | |
|---|
| 21 | """ |
|---|
| 22 | Use to create documentation of the protocol |
|---|
| 23 | """ |
|---|
| 24 | |
|---|
| 25 | # init translation |
|---|
| 26 | import gettext, inspect |
|---|
| 27 | from deejayd.ui.i18n import DeejaydTranslations |
|---|
| 28 | try: t = gettext.translation("deejayd", class_=DeejaydTranslations) |
|---|
| 29 | except IOError: |
|---|
| 30 | t = DeejaydTranslations() |
|---|
| 31 | t.install() |
|---|
| 32 | |
|---|
| 33 | from deejayd.mediafilters import * |
|---|
| 34 | from deejayd import DeejaydSignal |
|---|
| 35 | from deejayd.jsonrpc import interfaces |
|---|
| 36 | from deejayd.jsonrpc.jsonbuilders import JSONRPCResponse, JSONRPCRequest,\ |
|---|
| 37 | Get_json_filter, DeejaydJSONSignal |
|---|
| 38 | |
|---|
| 39 | common_request = [ |
|---|
| 40 | {"prefix": "", "desc": "General Commands",\ |
|---|
| 41 | "object": interfaces.CoreModule}, |
|---|
| 42 | {"prefix": "introspection.", "desc": "Introspection Commands",\ |
|---|
| 43 | "object": interfaces.IntrospectionModule}, |
|---|
| 44 | {"prefix": "player.", "desc": "Player Commands",\ |
|---|
| 45 | "object": interfaces.PlayerModule}, |
|---|
| 46 | {"prefix": "audiolib.", "desc": "Audio Library Commands",\ |
|---|
| 47 | "object": interfaces.LibraryModule}, |
|---|
| 48 | {"prefix": "videolib.", "desc": "Video Library Commands",\ |
|---|
| 49 | "object": interfaces.LibraryModule}, |
|---|
| 50 | {"prefix": "playlist.", "desc": "Playlist Mode Commands",\ |
|---|
| 51 | "object": interfaces.PlaylistSourceModule}, |
|---|
| 52 | {"prefix": "panel.", "desc": "Panel Mode Commands",\ |
|---|
| 53 | "object": interfaces.PanelSourceModule}, |
|---|
| 54 | {"prefix": "video.", "desc": "Video Mode Commands",\ |
|---|
| 55 | "object": interfaces.VideoSourceModule}, |
|---|
| 56 | {"prefix": "webradio.", "desc": "Webradio Mode Commands",\ |
|---|
| 57 | "object": interfaces.WebradioSourceModule}, |
|---|
| 58 | {"prefix": "dvd.", "desc": "Dvd Mode Commands",\ |
|---|
| 59 | "object": interfaces.DvdSourceModule}, |
|---|
| 60 | {"prefix": "queue.", "desc": "Queue Commands",\ |
|---|
| 61 | "object": interfaces.QueueModule}, |
|---|
| 62 | {"prefix": "recpls.", "desc": "Recorded Playlist Commands",\ |
|---|
| 63 | "object": interfaces.RecordedPlaylistModule}, |
|---|
| 64 | ] |
|---|
| 65 | |
|---|
| 66 | class WikiFormat: |
|---|
| 67 | |
|---|
| 68 | def commandDoc(self): |
|---|
| 69 | return """ |
|---|
| 70 | As written in specification, request is like that : |
|---|
| 71 | {{{ |
|---|
| 72 | `%(request)s` |
|---|
| 73 | }}} |
|---|
| 74 | |
|---|
| 75 | """ % { |
|---|
| 76 | "request": JSONRPCRequest("method_name",\ |
|---|
| 77 | ["params1", "params2"], id="id").to_pretty_json(),\ |
|---|
| 78 | } |
|---|
| 79 | |
|---|
| 80 | def answerDoc(self): |
|---|
| 81 | return """ |
|---|
| 82 | As written in specification, response is like that : |
|---|
| 83 | {{{ |
|---|
| 84 | %s |
|---|
| 85 | }}} |
|---|
| 86 | |
|---|
| 87 | For deejayd, result parameter has always the same syntax : |
|---|
| 88 | {{{ |
|---|
| 89 | `{ |
|---|
| 90 | "type": answer_type, |
|---|
| 91 | "answer": the real answer value |
|---|
| 92 | }` |
|---|
| 93 | }}} |
|---|
| 94 | With response types equals to: |
|---|
| 95 | * ack |
|---|
| 96 | * list |
|---|
| 97 | * dict |
|---|
| 98 | * mediaList |
|---|
| 99 | * dvdInfo |
|---|
| 100 | * fileAndDirList |
|---|
| 101 | """ % JSONRPCResponse("deejayd_response", "id").to_pretty_json() |
|---|
| 102 | |
|---|
| 103 | def formatSectionDoc(self, section): |
|---|
| 104 | cmds = [] |
|---|
| 105 | for c_name in dir(section["object"]): |
|---|
| 106 | if not c_name.startswith("__"): |
|---|
| 107 | try: c = getattr(section["object"], c_name) |
|---|
| 108 | except AttributeError: |
|---|
| 109 | continue |
|---|
| 110 | if inspect.isclass(c): cmds.append(c) |
|---|
| 111 | return """ |
|---|
| 112 | === `%(section)s` === |
|---|
| 113 | |
|---|
| 114 | %(commands)s |
|---|
| 115 | """ % { |
|---|
| 116 | "section": section["desc"], |
|---|
| 117 | "commands": "\n\n".join(map(self.formatCommandDoc, cmds,\ |
|---|
| 118 | [section["prefix"] for i in range(len(cmds))])), |
|---|
| 119 | } |
|---|
| 120 | |
|---|
| 121 | def formatCommandDoc(self, cmd, prefix = ""): |
|---|
| 122 | args = '' |
|---|
| 123 | |
|---|
| 124 | command_args = getattr(cmd, "args", []) |
|---|
| 125 | for arg in command_args: |
|---|
| 126 | props = [] |
|---|
| 127 | |
|---|
| 128 | # An argument is optional by default |
|---|
| 129 | if 'req' not in arg.keys(): |
|---|
| 130 | arg['req'] = False |
|---|
| 131 | if arg['req']: |
|---|
| 132 | props.append('Mandatory') |
|---|
| 133 | else: |
|---|
| 134 | props.append('Optional') |
|---|
| 135 | |
|---|
| 136 | args += " * {{{%(name)s}}} (%(props)s) : %(type)s\n"\ |
|---|
| 137 | % { 'name': arg['name'], |
|---|
| 138 | 'props': ' and '.join(props), |
|---|
| 139 | 'type' : arg['type'] } |
|---|
| 140 | |
|---|
| 141 | if len(command_args) == 0: |
|---|
| 142 | args = " * ''This command does not accept any argument.''\n" |
|---|
| 143 | |
|---|
| 144 | rvalues = None |
|---|
| 145 | try: |
|---|
| 146 | if isinstance(cmd.answer, list): |
|---|
| 147 | rvalues = cmd.answer |
|---|
| 148 | else: |
|---|
| 149 | rvalues = [cmd.answer] |
|---|
| 150 | except AttributeError: |
|---|
| 151 | rvalues = ['ack'] |
|---|
| 152 | |
|---|
| 153 | return """==== `%(name)s` ==== |
|---|
| 154 | |
|---|
| 155 | %(desc)s |
|---|
| 156 | |
|---|
| 157 | Arguments : |
|---|
| 158 | %(args)s |
|---|
| 159 | Expected return value : ''`%(rvalues)s`'' |
|---|
| 160 | |
|---|
| 161 | """ % { 'name' : prefix+cmd.__name__, |
|---|
| 162 | 'desc' : cmd.__doc__.strip('\n'), |
|---|
| 163 | 'args' : args, |
|---|
| 164 | 'rvalues' : rvalues } |
|---|
| 165 | |
|---|
| 166 | def build(self, sections): |
|---|
| 167 | filter = And(Equals("artist", "artist_name"),\ |
|---|
| 168 | Or(Contains("genre", "Rock"), Higher("Rating", "4"))) |
|---|
| 169 | signal = DeejaydSignal("signal_name", {"attr1": "value1"}) |
|---|
| 170 | return """= deejayd - JSON-RPC Protocol = |
|---|
| 171 | |
|---|
| 172 | Deejayd protocol follows JSON-RPC 1.0 specification available |
|---|
| 173 | [http://json-rpc.org/wiki/specification here]. |
|---|
| 174 | All data between the client and server is encoded in UTF-8. |
|---|
| 175 | |
|---|
| 176 | == Commands Format == |
|---|
| 177 | |
|---|
| 178 | %(cmd_format)s |
|---|
| 179 | |
|---|
| 180 | == Response Format == |
|---|
| 181 | |
|---|
| 182 | %(answer)s |
|---|
| 183 | |
|---|
| 184 | == Separator == |
|---|
| 185 | |
|---|
| 186 | Commands and Responses always finish with the separator `ENDJSON\\n`. |
|---|
| 187 | So a command is interpreted by deejayd server only if it finish with this |
|---|
| 188 | separator. |
|---|
| 189 | |
|---|
| 190 | == Specific Objects == |
|---|
| 191 | |
|---|
| 192 | === Mediafilter Objects === |
|---|
| 193 | |
|---|
| 194 | Mediafilter object has been serialized in a specific way to be passed as |
|---|
| 195 | an method argument or receive with an answer. An example is given here. |
|---|
| 196 | {{{ |
|---|
| 197 | `%(filter)s` |
|---|
| 198 | }}} |
|---|
| 199 | |
|---|
| 200 | === Sort Objects === |
|---|
| 201 | |
|---|
| 202 | Sort object has been serialized in a specific way to be passed as |
|---|
| 203 | an method argument or receive with an answer. An example is given here. |
|---|
| 204 | {{{ |
|---|
| 205 | `[["tag1", "ascending"], ["tag2", "descending"]]` |
|---|
| 206 | }}} |
|---|
| 207 | |
|---|
| 208 | Possible sort values are : "ascending" and "descending". |
|---|
| 209 | |
|---|
| 210 | === Signal Objects === |
|---|
| 211 | |
|---|
| 212 | Signal is available for TCP connection only. |
|---|
| 213 | Signal object has been serialized in a specific way to be send to client. |
|---|
| 214 | An example is given here. |
|---|
| 215 | {{{ |
|---|
| 216 | `%(signal)s` |
|---|
| 217 | }}} |
|---|
| 218 | |
|---|
| 219 | == Common Available Commands == |
|---|
| 220 | |
|---|
| 221 | %(commands)s |
|---|
| 222 | |
|---|
| 223 | == Http Specific Commands == |
|---|
| 224 | |
|---|
| 225 | %(web_commands)s |
|---|
| 226 | |
|---|
| 227 | == TCP commands == |
|---|
| 228 | |
|---|
| 229 | %(tcp_commands)s |
|---|
| 230 | """ % { |
|---|
| 231 | "cmd_format": self.commandDoc(), |
|---|
| 232 | "answer": self.answerDoc(), |
|---|
| 233 | "filter": Get_json_filter(filter).to_pretty_json(), |
|---|
| 234 | "commands": "\n\n".join(map(self.formatSectionDoc, sections)), |
|---|
| 235 | "signal": DeejaydJSONSignal(signal).to_pretty_json(), |
|---|
| 236 | "web_commands": self.formatSectionDoc({\ |
|---|
| 237 | "prefix": "web.", |
|---|
| 238 | "desc": "Commands specific to webui", |
|---|
| 239 | "object": interfaces.WebModule, |
|---|
| 240 | }), |
|---|
| 241 | "tcp_commands": self.formatSectionDoc({\ |
|---|
| 242 | "prefix": "tcp.", |
|---|
| 243 | "desc": "TCP Specific commands (ex: signal commands)", |
|---|
| 244 | "object": interfaces.TcpModule, |
|---|
| 245 | }), |
|---|
| 246 | } |
|---|
| 247 | |
|---|
| 248 | if __name__ == "__main__": |
|---|
| 249 | docs = WikiFormat().build(common_request) |
|---|
| 250 | |
|---|
| 251 | f = open("doc/deejayd_rpc_protocol","w") |
|---|
| 252 | try: f.write(docs) |
|---|
| 253 | finally: f.close() |
|---|
| 254 | |
|---|
| 255 | |
|---|
| 256 | # vim: ts=4 sw=4 expandtab |
|---|