file_server.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. #
  2. # Copyright (C) 2014-2015 UAVCAN Development Team <uavcan.org>
  3. #
  4. # This software is distributed under the terms of the MIT License.
  5. #
  6. # Author: Ben Dyer <ben_dyer@mac.com>
  7. # Pavel Kirienko <pavel.kirienko@zubax.com>
  8. #
  9. from __future__ import division, absolute_import, print_function, unicode_literals
  10. import os
  11. from collections import defaultdict
  12. from logging import getLogger
  13. import uavcan
  14. import errno
  15. logger = getLogger(__name__)
  16. def _try_resolve_relative_path(search_in, rel_path):
  17. rel_path = os.path.normcase(os.path.normpath(rel_path))
  18. for p in search_in:
  19. p = os.path.normcase(os.path.abspath(p))
  20. if p.endswith(rel_path) and os.path.isfile(p):
  21. return p
  22. joined = os.path.join(p, rel_path)
  23. if os.path.isfile(joined):
  24. return joined
  25. # noinspection PyBroadException
  26. class FileServer(object):
  27. def __init__(self, node, lookup_paths=None):
  28. if node.is_anonymous:
  29. raise uavcan.UAVCANException('File server cannot be launched on an anonymous node')
  30. self.lookup_paths = lookup_paths or []
  31. self._path_hit_counters = defaultdict(int)
  32. self._handles = []
  33. def add_handler(datatype, callback):
  34. self._handles.append(node.add_handler(datatype, callback))
  35. add_handler(uavcan.protocol.file.GetInfo, self._get_info)
  36. add_handler(uavcan.protocol.file.Read, self._read)
  37. # TODO: support all file services
  38. def close(self):
  39. for x in self._handles:
  40. x.remove()
  41. @property
  42. def path_hit_counters(self):
  43. return dict(self._path_hit_counters)
  44. def _resolve_path(self, relative):
  45. rel = relative.path.decode().replace(chr(relative.SEPARATOR), os.path.sep)
  46. out = _try_resolve_relative_path(self.lookup_paths, rel)
  47. if not out:
  48. raise OSError(errno.ENOENT)
  49. self._path_hit_counters[out] += 1
  50. return out
  51. def _get_info(self, e):
  52. logger.debug("[#{0:03d}:uavcan.protocol.file.GetInfo] {1!r}"
  53. .format(e.transfer.source_node_id, e.request.path.path.decode()))
  54. try:
  55. with open(self._resolve_path(e.request.path), "rb") as f:
  56. data = f.read()
  57. resp = uavcan.protocol.file.GetInfo.Response()
  58. resp.error.value = resp.error.OK
  59. resp.size = len(data)
  60. resp.entry_type.flags = resp.entry_type.FLAG_FILE | resp.entry_type.FLAG_READABLE
  61. except Exception:
  62. # TODO: Convert OSError codes to the error codes defined in DSDL
  63. logger.exception("[#{0:03d}:uavcan.protocol.file.GetInfo] error", exc_info=True)
  64. resp = uavcan.protocol.file.GetInfo.Response()
  65. resp.error.value = resp.error.UNKNOWN_ERROR
  66. return resp
  67. def _read(self, e):
  68. logger.debug("[#{0:03d}:uavcan.protocol.file.Read] {1!r} @ offset {2:d}"
  69. .format(e.transfer.source_node_id, e.request.path.path.decode(), e.request.offset))
  70. try:
  71. with open(self._resolve_path(e.request.path), "rb") as f:
  72. f.seek(e.request.offset)
  73. resp = uavcan.protocol.file.Read.Response()
  74. read_size = uavcan.get_uavcan_data_type(uavcan.get_fields(resp)['data']).max_size
  75. resp.data = bytearray(f.read(read_size))
  76. resp.error.value = resp.error.OK
  77. except Exception:
  78. logger.exception("[#{0:03d}:uavcan.protocol.file.Read] error")
  79. resp = uavcan.protocol.file.Read.Response()
  80. resp.error.value = resp.error.UNKNOWN_ERROR
  81. return resp