PageRenderTime 71ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 0ms

/svxlinkconf.py

https://github.com/eren/python-svxlinkconf
Python | 447 lines | 419 code | 7 blank | 21 comment | 5 complexity | 3c5b4d2d40ed87f4d8f6bae77def0ced MD5 | raw file
  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. import sys
  4. # collections.OrderedDict is not available in older
  5. # versions of Python. Import an OrderedDict for
  6. # older versions
  7. if (2,7) > sys.version_info:
  8. from ordereddict import OrderedDict
  9. else:
  10. from collections import OrderedDict
  11. import iniparse
  12. from ConfigParser import NoSectionError, \
  13. NoOptionError
  14. """
  15. This module provides a way of manipulating svxlink.conf.
  16. .. moduleauthor:: Eren Türkay <eren@pardus.org.tr>
  17. """
  18. __author__ = "Eren Türkay"
  19. __email__ = "eren@pardus.org.tr"
  20. __copyright__ = "Copyright 2011"
  21. __license__ = "GPLv2"
  22. __version__ = "0.1"
  23. class SvxlinkTypeContainer(object):
  24. def __init__(self, type_name, section_name, valid_options, data=None):
  25. """Type container for Svxlink Types.
  26. It serves as an abstract class. We need to check for valid options
  27. when setting sections. Instead of copying/pasting the code, this
  28. abstract class checks for valid options when setting an item in class.
  29. __dbase__ is an internal representation of key/values with an
  30. OrderedDict. If data is provided, __dbase__ is filled with this
  31. data. Otherwise, we know that the section is created from
  32. scratch so we add TYPE accordingly to type_name argument by
  33. default. "Data" is an array of tuple because it's the type that
  34. ConfigParser returns. For the ease of use, it's directly used in
  35. this way.
  36. Note that svxlink.conf requires UPPERCASE for options in sections.
  37. Section names can be arbitrary, however, options are presented
  38. upper-case. Whenever you set an option name, it will be converted to
  39. uppercase. It does not matter the way you set options. For
  40. example:
  41. f = SvxlinkTypeNet("foo")
  42. f["tcp_PORT" = 5220
  43. is converted to:
  44. f["TCP_PORT"] 5220
  45. so it's still valid to use as long as option is present in
  46. VALID_OPTIONS
  47. """
  48. # TODO: Implement a checker function. Now, we only check for
  49. # valid options, not the values themselves. Later, we would need
  50. # to check for values so we need to implement a function that
  51. # checks it. Additionally, this function will be unique to the
  52. # classes that extends SvxlinkTypeContainer. So it should be
  53. # optional in __init__()
  54. self._VALID_OPTIONS = valid_options
  55. self._TYPE_NAME = type_name
  56. self._SECTION_NAME = section_name
  57. # internal ordered dictionary for storing key/values typical to
  58. # section
  59. self.__dbase__ = OrderedDict()
  60. if data is None:
  61. self.__dbase__.update({"TYPE": type_name})
  62. else:
  63. # start adding values that are in tuple to __dict__
  64. for tuple_item in data:
  65. self.__check_item_and_update(tuple_item[0],
  66. tuple_item[1])
  67. def __check_item_and_update(self, key, val):
  68. """Checks the item in VALID_OPTIONS and updates __dbase__ if the
  69. option is valid.
  70. """
  71. if not key.upper() in self._VALID_OPTIONS:
  72. raise ValueError("Option '%s' is not valid for '%s'" %
  73. (key, self._SECTION_NAME))
  74. self.__dbase__.update({key.upper(): val})
  75. def __str__(self):
  76. return "<SvxlinkType%s: %s>" % (self._TYPE_NAME, self._SECTION_NAME)
  77. def __getitem__(self, key):
  78. return self.__dbase__.get(key.upper());
  79. def __setitem__(self, key, val):
  80. self.__check_item_and_update(key, val)
  81. def __eq__(self, other):
  82. # compare any object with our section name.
  83. return other == self._SECTION_NAME
  84. def get_section_name(self):
  85. """Returns a section name"""
  86. return self._SECTION_NAME
  87. def has_option(self, option):
  88. """Checks if there is an option in __dict__
  89. """
  90. return self.__dbase__.has_key(option.upper())
  91. def items(self):
  92. """Returns ConfigParser compatable output for items in this section.
  93. The output is an array of tuples such as:
  94. [(tcp_port, 5220), (type, "Net")]
  95. """
  96. # iterate over __dict__, do not take variables that start with _
  97. # into account.
  98. output = []
  99. for item in self.__dbase__:
  100. if not item.startswith("_"):
  101. output.append((item, self[item]))
  102. return output
  103. def is_online(self):
  104. """An abstract method for checking if the section is up.
  105. This method should be implemented in SvxlinkType objects. By
  106. default, it returns true. For example, for a
  107. SvxlinkTypeRepeater, is_online() method can check if the
  108. repeater is in LOGICS option in GLOBAL section. For a Local
  109. device, this method can check if the card is listed by ALSA and
  110. can be accessed without a problem.
  111. """
  112. return True
  113. class SvxlinkTypeNet(SvxlinkTypeContainer):
  114. """The class that represents TYPE=Net section
  115. """
  116. def __init__(self, section_name, data=None):
  117. """
  118. :param section_name: Name of the section.
  119. :param data: **(Optional)** An array of (name, value) pair. For
  120. example [('host', 'localhost'), ('tcp_port', '5220')]
  121. """
  122. # TODO: Add SPEEX_ENC_*, SPEEX_DEC_* options here
  123. super(SvxlinkTypeNet, self).__init__("Net", section_name,
  124. ["TYPE", "HOST", "TCP_PORT", "AUTH_KEY", "CODEC"],
  125. data)
  126. def is_online(self):
  127. """Checks if the host is up and running.
  128. """
  129. if not (self.has_option("TCP_PORT") and
  130. self.has_option("HOST")):
  131. raise ValueError("TCP_PORT and HOST should be set for this function to run")
  132. # FIXME: Maybe we should import it at top?
  133. import socket
  134. # we need 3 second delay at most, otherwise we may think that
  135. # host is not running.
  136. socket.setdefaulttimeout(0.5)
  137. s = socket.socket(socket.AF_INET,
  138. socket.SOCK_STREAM)
  139. try:
  140. s.connect((self["HOST"], int(self["TCP_PORT"])))
  141. return True
  142. except:
  143. # FIXME: For debug purposes, we need to get what the
  144. # exception was
  145. return False
  146. class SvxlinkTypeMulti(SvxlinkTypeContainer):
  147. """The class that represents TYPE=Multi section
  148. """
  149. def __init__(self, section_name, data=None):
  150. """
  151. :param section_name: Name of the section.
  152. :param data: **(Optional)** An array of (name, value) pair. For
  153. example [('transmitters', 'Istanbul,Ankara')]
  154. """
  155. super(SvxlinkTypeMulti, self).__init__("Multi", section_name,
  156. ["TYPE", "TRANSMITTERS"],
  157. data)
  158. class SvxlinkTypeVoter(SvxlinkTypeContainer):
  159. """The class that represents TYPE=Voter section
  160. """
  161. def __init__(self, section_name, data=None):
  162. """
  163. :param section_name: Name of the section.
  164. :param data: **(Optional)** An array of (name, value) pair. For
  165. example [('receivers', 'Istanbul,Ankara')]
  166. """
  167. super(SvxlinkTypeVoter, self).__init__("Voter", section_name,
  168. ["TYPE", "RECEIVERS", "VOTING_DELAY", "BUFFER_LENGTH"],
  169. data)
  170. class SvxlinkTypeLocal(SvxlinkTypeContainer):
  171. """The class that represents TYPE=Local section
  172. """
  173. def __init__(self, section_name, data=None):
  174. """
  175. :param section_name: Name of the section.
  176. :param data: **(Optional)** An array of (name, value) pair. For
  177. example [('audio_dev', 'alsa:plughw:0'), ('audio_channel', 0)]
  178. """
  179. # FIXME: These are not all valid options. I use only these
  180. # options so I added them for rapid development. Will add other
  181. # options as well
  182. super(SvxlinkTypeLocal, self).__init__("Local", section_name,
  183. ['TYPE', 'AUDIO_DEV', 'AUDIO_CHANNEL', 'SQL_DET',
  184. 'SQL_START_DELAY', 'SQL_DELAY', 'SQL_HANGTIME',
  185. 'VOX_FILTER_DEPTH', 'VOX_THRESH', 'CTCSS_FQ',
  186. 'CTCSS_THRESH', 'DEEMPHASIS', 'DTMF_DEC_TYPE',
  187. 'SERIAL_PORT', 'PTT_PIN', 'PTT_PORT', 'TX_DELAY',
  188. 'TIMEOUT', 'PREEMPHASIS'],
  189. data)
  190. class SvxlinkConf():
  191. """Main class
  192. """
  193. def __init__(self, config_file="/etc/svxlink/svxlink.conf"):
  194. """Initialize the class.
  195. Initializes the class. Be sure that the file exists before
  196. using this class as we do not control whether the file exists or
  197. not
  198. This is an abstraction layer over ConfigParser. If you feel that
  199. this class lacks some support for manipulating configuration
  200. files, SvxlinkConf.parser can be used. This is an instance of
  201. iniparser.ConfigParser().
  202. :param config_file: Config file to read or manipulate ** (default:
  203. /etc/svxlink/svxlink.conf)**
  204. :type config_file: string
  205. """
  206. parser = iniparse.ConfigParser()
  207. parser.read(config_file)
  208. self.config_file = config_file
  209. self.config = parser
  210. # mappers to be used in get_section() method. We define here
  211. # what class should be used accordingly to TYPE= variable.
  212. self._TYPES = {"Net": SvxlinkTypeNet,
  213. "Voter": SvxlinkTypeVoter,
  214. "Local": SvxlinkTypeLocal,
  215. "Multi": SvxlinkTypeMulti}
  216. def is_item_present_in_pair(self, item, data):
  217. """Checks if there is an item in an array of (key, value) pair.
  218. :param data: An array of (key, value) pair. For example:
  219. [('type', 'Voter'), ('receivers', 'Rx1,Local'), ('voting_delay',
  220. '200')]
  221. :type data: Array
  222. :returns: True or False
  223. :rtype: Boolean
  224. """
  225. for i in data:
  226. if i[0] == item:
  227. return True
  228. else:
  229. continue
  230. return False
  231. def get_remote_nodes(self):
  232. """Lists nodes that are TYPE=Net
  233. Iterate over TYPE=Net section and list the remote nodes as
  234. SvxlinkTypeNet object.
  235. :returns: An array of SvxlinkTypeNet object.
  236. :rtype: array
  237. """
  238. sections = self.config.sections()
  239. # Get sections which have "TYPE" option. GLOBAL for example does
  240. # not have this option so we would get NoOptionError exception.
  241. # Additionally, filter only Net type
  242. remote_nodes = filter(lambda x: (self.config.has_option(x, "TYPE") and
  243. self.config.get(x, "TYPE") == "Net"), sections)
  244. return map(lambda x: SvxlinkTypeNet(x, self.config.items(x)), remote_nodes)
  245. def add_section(self, sectionObj):
  246. """Adds a section to main svxlink.conf.
  247. :param sectionObj: An object to add
  248. :type sectionObj: One of the SvxlinkType* objects.
  249. """
  250. section_name = sectionObj.get_section_name()
  251. self.config.add_section(section_name)
  252. for item in sectionObj.items():
  253. self.config.set(section_name, item[0], item[1])
  254. def update_section(self, sectionObj):
  255. """Updates the section accordingly to one of the SvxlinkType
  256. objects.
  257. Note that the section should be present before updating.
  258. :param sectionObjy: sectionObj: One of SvxlinkType objects
  259. """
  260. # check if section is present
  261. section_name = sectionObj.get_section_name()
  262. if not section_name in self.config.sections():
  263. raise NoSectionError(section_name)
  264. # remote the section first
  265. self.config.remove_section(section_name)
  266. # add it
  267. self.add_section(sectionObj)
  268. def get_section(self, section_name):
  269. """Returns one of the SvxlinkType objects accordingly to TYPE=
  270. value of the section.
  271. If there are no SvxlinkType classes associated with the value,
  272. an array of (key, value) pair is returned.
  273. :param section_name: Name of the section to get
  274. :type section_name: String.
  275. """
  276. # check if section is present in config
  277. if not section_name in self.config.sections():
  278. raise NoSectionError(section_name)
  279. # TODO: We would want to handle different sections, so remember
  280. # this line that even if TYPE is not present, we would want to
  281. # handle it differently instead of returning items
  282. # check if option is present. If TYPE is not present, just
  283. # return items
  284. if not self.is_item_present_in_pair("type",
  285. self.config.items(section_name)):
  286. return self.config.items(section_name)
  287. # now we know TYPE is present, try to find associated class.
  288. type_name = self.config.get(section_name, "TYPE")
  289. if not type_name in self._TYPES:
  290. # nothing to return as an object, return items
  291. return self.config.items(section_name)
  292. else:
  293. obj = self._TYPES[type_name]
  294. return obj(section_name, self.config.items(section_name))
  295. def write(self, file_name=None, mode="w+"):
  296. """Save changes in the configuration file
  297. :param file_name: File name to write to
  298. :type file_name: String
  299. :param mode: Mode that is used to open the file. **(Default: a)**
  300. :type mode: String
  301. """
  302. if not file_name:
  303. file_name = self.config_file
  304. fp = open(file_name, mode)
  305. self.config.write(fp)
  306. fp.close()
  307. def foo(self):
  308. #f = SvxlinkTypeNet("ErenTurkay", [("tcp_port", "5220"), ("auth_key", "testtest")])
  309. f = self.get_section("Rx1")
  310. print f
  311. print f.is_online()
  312. if __name__ == '__main__':
  313. f = SvxlinkConf("etc/svxlink.conf")
  314. # for remote in f.get_remote_nodes():
  315. # print "%s" % remote
  316. f.foo()