You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

300 lines
12 KiB

  1. """Command line client functionality, broken into a separate file
  2. so it can be more easily tested."""
  3. from __future__ import absolute_import
  4. from nilmdb.printf import *
  5. import nilmdb.client
  6. import nilmdb.layout
  7. import nilmdb.timestamper
  8. import time
  9. import sys
  10. import re
  11. import os
  12. import urlparse
  13. import argparse
  14. import fnmatch
  15. import subprocess
  16. from argparse import ArgumentDefaultsHelpFormatter as def_form
  17. version = "0.1"
  18. class Cmdline(object):
  19. def __init__(self, argv):
  20. self.argv = argv
  21. def parser_setup(self):
  22. version_string = sprintf("nilmtool %s, client library %s",
  23. version, nilmdb.Client.client_version)
  24. self.parser = argparse.ArgumentParser(add_help = False,
  25. formatter_class = def_form)
  26. group = self.parser.add_argument_group("General options")
  27. group.add_argument("-q", "--quiet", action='store_true',
  28. help='suppress unnecessary messages')
  29. group.add_argument("-h", "--help", action='help',
  30. help='show this help message and exit')
  31. group.add_argument("-V", "--version", action="version",
  32. version=version_string)
  33. group = self.parser.add_argument_group("Server")
  34. group.add_argument("-u", "--url", action="store",
  35. default="http://localhost:12380/",
  36. help="NilmDB server URL (default: %(default)s)")
  37. sub = self.parser.add_subparsers(title="Commands",
  38. dest="command",
  39. description="Specify --help after "
  40. "the command for command-specific "
  41. "options.")
  42. self.parser_setup_info(sub)
  43. self.parser_setup_list(sub)
  44. self.parser_setup_create(sub)
  45. self.parser_setup_metadata(sub)
  46. self.parser_setup_insert(sub)
  47. def parser_setup_info(self, sub):
  48. cmd = sub.add_parser("info", help="Server information",
  49. formatter_class = def_form,
  50. description="""
  51. List information about the server, like
  52. version.
  53. """)
  54. cmd.set_defaults(handler = self.cmd_info)
  55. def parser_setup_list(self, sub):
  56. cmd = sub.add_parser("list", help="List streams",
  57. formatter_class = def_form,
  58. description="""
  59. List streams available in the database,
  60. optionally filtering by layout or path. Wildcards
  61. are accepted.
  62. """)
  63. cmd.set_defaults(handler = self.cmd_list)
  64. group = cmd.add_argument_group("Stream filtering")
  65. group.add_argument("-l", "--layout", default="*",
  66. help="Match only this stream layout")
  67. group.add_argument("-p", "--path", default="*",
  68. help="Match only this path")
  69. def parser_setup_create(self, sub):
  70. cmd = sub.add_parser("create", help="Create a new stream",
  71. formatter_class = def_form,
  72. description="""
  73. Create a new empty stream at the
  74. specified path and with the specifed
  75. layout type.
  76. """)
  77. cmd.set_defaults(handler = self.cmd_create)
  78. group = cmd.add_argument_group("Required arguments")
  79. group.add_argument("path",
  80. help="Path of new stream, e.g. /foo/bar")
  81. group.add_argument("layout",
  82. help="Layout type for new stream, e.g. RawData")
  83. def parser_setup_metadata(self, sub):
  84. cmd = sub.add_parser("metadata", help="Get or set stream metadata",
  85. description="""
  86. Get or set key=value metadata associated with
  87. a stream.
  88. """,
  89. usage="%(prog)s path [-g [key ...] | "
  90. "-s key=value [...] | -u key=value [...]]")
  91. cmd.set_defaults(handler = self.cmd_metadata)
  92. group = cmd.add_argument_group("Required arguments")
  93. group.add_argument("path",
  94. help="Path of stream, e.g. /foo/bar")
  95. group = cmd.add_argument_group("Actions")
  96. exc = group.add_mutually_exclusive_group()
  97. exc.add_argument("-g", "--get", nargs="*", metavar="key",
  98. help="Get metadata for specified keys (default all)")
  99. exc.add_argument("-s", "--set", nargs="+", metavar="key=value",
  100. help="Replace all metadata with provided "
  101. "key=value pairs")
  102. exc.add_argument("-u", "--update", nargs="+", metavar="key=value",
  103. help="Update metadata using provided "
  104. "key=value pairs")
  105. def parser_setup_insert(self, sub):
  106. cmd = sub.add_parser("insert", help="Insert data",
  107. description="""
  108. Insert data into a stream.
  109. """)
  110. cmd.set_defaults(handler = self.cmd_insert)
  111. group = cmd.add_argument_group("Timestamping",
  112. description="""
  113. If timestamps are already provided in the
  114. input date, use --none. Otherwise,
  115. provide --start, or use --filename to
  116. try to deduce timestamps from the file.
  117. """)
  118. group.add_argument("-r", "--rate", type=float,
  119. help="""
  120. If needed, rate in Hz (default: based on
  121. stream layout)
  122. """)
  123. exc = group.add_mutually_exclusive_group()
  124. exc.add_argument("-s", "--start", metavar="TIME",
  125. help="Starting timestamp (free-form)")
  126. exc.add_argument("-f", "--filename", action="store_true",
  127. help="""
  128. Use filenames to determine start time
  129. (default, if filenames are provided)
  130. """)
  131. exc.add_argument("-n", "--none", action="store_true",
  132. help="Timestamp is already present, don't add one")
  133. group = cmd.add_argument_group("Required parameters")
  134. group.add_argument("path",
  135. help="Path of stream, e.g. /foo/bar")
  136. group.add_argument("file", nargs="*", default=['-'],
  137. help="File(s) to insert (default: stdin)")
  138. def die(self, formatstr, *args):
  139. fprintf(sys.stderr, formatstr, *args)
  140. self.client.close()
  141. sys.exit(-1)
  142. def run(self):
  143. # Run parser
  144. self.parser_setup()
  145. self.args = self.parser.parse_args(self.argv)
  146. self.client = nilmdb.Client(self.args.url)
  147. # Make a test connection to make sure things work
  148. try:
  149. server_version = self.client.version()
  150. except nilmdb.client.NilmCommError as e:
  151. self.die("Error connecting to server: %s\n", str(e))
  152. # Now dispatch client request to appropriate function. Parser
  153. # should have ensured that we don't have any unknown commands
  154. # here.
  155. self.args.handler()
  156. self.client.close()
  157. sys.exit(0)
  158. def cmd_info(self):
  159. """Print info about the server"""
  160. printf("Client library version: %s\n", self.client.client_version)
  161. printf("Server version: %s\n", self.client.version())
  162. printf("Server URL: %s\n", self.client.geturl())
  163. printf("Server database: %s\n", self.client.dbpath())
  164. def cmd_list(self):
  165. """List available streams"""
  166. streams = self.client.stream_list()
  167. for (path, layout) in streams:
  168. if (fnmatch.fnmatch(path, self.args.path) and
  169. fnmatch.fnmatch(layout, self.args.layout)):
  170. printf("%s %s\n", path, layout)
  171. def cmd_create(self):
  172. """Create new stream"""
  173. try:
  174. self.client.stream_create(self.args.path, self.args.layout)
  175. except nilmdb.client.ClientError as e:
  176. self.die("Error creating stream: %s\n", str(e))
  177. def cmd_metadata(self):
  178. """Manipulate metadata"""
  179. if self.args.set is not None or self.args.update is not None:
  180. # Either set, or update
  181. if self.args.set is not None:
  182. keyvals = self.args.set
  183. handler = self.client.stream_set_metadata
  184. else:
  185. keyvals = self.args.update
  186. handler = self.client.stream_update_metadata
  187. # Extract key=value pairs
  188. data = {}
  189. for keyval in keyvals:
  190. kv = keyval.split('=', 1)
  191. if len(kv) != 2 or kv[0] == "":
  192. self.die("Error parsing key=value argument '%s'\n", keyval)
  193. data[kv[0]] = kv[1]
  194. # Make the call
  195. try:
  196. handler(self.args.path, data)
  197. except nilmdb.client.ClientError as e:
  198. self.die("Error setting/updating metadata: %s\n", str(e))
  199. else:
  200. # Get (or unspecified)
  201. keys = self.args.get or None
  202. try:
  203. data = self.client.stream_get_metadata(self.args.path, keys)
  204. except nilmdb.client.ClientError as e:
  205. self.die("Error getting metadata: %s\n", str(e))
  206. for key, value in sorted(data.items()):
  207. # Omit nonexistant keys
  208. if value is None:
  209. value = ""
  210. printf("%s=%s\n", key, value)
  211. def cmd_insert(self):
  212. # Find requested stream
  213. streams = self.client.stream_list(self.args.path)
  214. if len(streams) != 1:
  215. self.die("Error getting stream info for path %s\n", self.args.path)
  216. layout = streams[0][1]
  217. if self.args.start and len(self.args.file) != 1:
  218. self.die("--start can only be used with one input file, for now")
  219. for filename in self.args.file:
  220. if filename == '-':
  221. process = None
  222. infile = sys.stdin
  223. else:
  224. if not os.path.exists(filename):
  225. self.die("Error opening input file %s\n", filename)
  226. try:
  227. # zcat is _much_ faster than python's gzopen
  228. process = subprocess.Popen(["zcat", "-f", filename],
  229. bufsize = -1,
  230. stdin = None,
  231. stderr = None,
  232. stdout = PIPE)
  233. infile = process.stdout
  234. except OSError: # pragma: no cover
  235. self.die("Error spawning zcat process\n")
  236. # Build a timestamper for this file
  237. if self.args.none:
  238. ts = nilmdb.timestamper.TimestamperNull(infile)
  239. else:
  240. # If no rate, see if we can get it from nilmdb.layout
  241. if not self.args.rate:
  242. try:
  243. self.args.rate = nilmdb.layout.named[layout].rate_hz
  244. except KeyError:
  245. self.die("Need to specify --rate\n")
  246. # These will die if they can't parse
  247. if self.args.start:
  248. start = self.parse_time(self.args.start)
  249. else:
  250. start = self.parse_time(filename)
  251. ts = nilmdb.timestamper.TimestamperRate(infile, start, rate)
  252. print "Input file:", filename
  253. print "Timestamper:", ts
  254. print "Start:", start
  255. print "Rate:", rate
  256. self.die("not implemented")