api.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. # encoding=utf-8
  2. import json
  3. import logging
  4. from tornado.web import HTTPError
  5. import tornado.websocket
  6. from terroroftinytown.tracker.base import BaseHandler
  7. from terroroftinytown.tracker.errors import (NoItemAvailable, UserIsBanned,
  8. InvalidClaim, FullClaim, UpdateClient, NoResourcesAvailable)
  9. from terroroftinytown.tracker.model import Project
  10. from terroroftinytown.tracker.stats import Stats, stats_bus
  11. from terroroftinytown.util.jsonutil import NativeStringJSONDecoder
  12. logger = logging.getLogger(__name__)
  13. class ProjectSettingsHandler(BaseHandler):
  14. def get(self):
  15. name = self.get_argument('name')
  16. project = Project.get_plain(name)
  17. if project:
  18. self.write(project.to_dict())
  19. else:
  20. raise HTTPError(404, reason='Project not found')
  21. class LiveStatsHandler(tornado.websocket.WebSocketHandler):
  22. def open(self):
  23. global stats_bus
  24. self.write_message({
  25. 'live': Stats.instance.get_live(),
  26. 'lifetime': Stats.instance.get_lifetime(),
  27. 'global': Stats.instance.get_global(),
  28. 'project': Stats.instance.get_project()
  29. })
  30. stats_bus += self.on_stats
  31. def on_stats(self, **stats):
  32. self.write_message({
  33. 'live_new': stats
  34. })
  35. def on_close(self):
  36. stats_bus.clear_handlers(self)
  37. class UserStatsHandler(BaseHandler):
  38. def get(self, username):
  39. stats = Stats.instance.get_user_lifetime(username)
  40. self.write({'stats': stats})
  41. class GetHandler(BaseHandler):
  42. def post(self):
  43. ip_address = self.request.remote_ip
  44. version = int(self.get_argument('version'))
  45. client_version = int(self.get_argument('client_version'))
  46. username = self.get_argument('username')
  47. user_agent = self.request.headers.get('User-Agent')
  48. try:
  49. claim = self.application.checkout_item(
  50. username, ip_address=ip_address, version=version,
  51. client_version=client_version
  52. )
  53. except NoItemAvailable:
  54. raise HTTPError(
  55. 404,
  56. reason='No free items available currently. '
  57. 'Don\'t worry; this is normal. '
  58. 'You will be assigned items soon.'
  59. )
  60. except UserIsBanned:
  61. raise HTTPError(
  62. 403,
  63. reason='You are banned. Please contact an administrator.'
  64. )
  65. except FullClaim:
  66. raise HTTPError(
  67. 429,
  68. reason=(
  69. 'No more items available for %s. '
  70. 'Don\'t worry; We limit 1 IP address per shortener. '
  71. 'Try again later.'
  72. % (ip_address)
  73. )
  74. )
  75. except UpdateClient as e:
  76. raise HTTPError(
  77. 412,
  78. reason=(
  79. 'Client needs update. '
  80. 'Library version: %s, min %s; '
  81. 'Pipeline version: %s, min %s. '
  82. 'Please restart Warrior.'
  83. % (
  84. e.version, e.current_version,
  85. e.client_version, e.current_client_version
  86. )
  87. )
  88. )
  89. except NoResourcesAvailable as e:
  90. raise HTTPError(
  91. 507,
  92. reason='The tracker needs an operator for manual maintenance. '
  93. 'Try again later.'
  94. )
  95. else:
  96. logger.info(
  97. 'User request: ip=%s user=%s '
  98. 'ver=%s client_ver=%s user_agent=%s',
  99. ip_address, repr(username),
  100. version, client_version, repr(user_agent)
  101. )
  102. logger.info('Checked out claim %s', claim)
  103. self.write(claim)
  104. class DoneHandler(BaseHandler):
  105. def post(self):
  106. claim_id = self.get_argument('claim_id')
  107. tamper_key = self.get_argument('tamper_key')
  108. results_str = self.get_argument('results')
  109. results = json.loads(results_str, cls=NativeStringJSONDecoder)
  110. try:
  111. stats = self.application.checkin_item(claim_id, tamper_key, results)
  112. except InvalidClaim:
  113. raise HTTPError(
  114. 409,
  115. reason='The item is invalid or '
  116. 'may have been already done by someone else.'
  117. )
  118. else:
  119. time_diff = stats['finished'] - stats['started']
  120. logger.info('Checked in claim %s. Len=%d, Time_diff=%d',
  121. claim_id,
  122. len(results),
  123. time_diff
  124. )
  125. self.write({'status': 'OK'})
  126. class ErrorHandler(BaseHandler):
  127. def post(self):
  128. claim_id = self.get_argument('claim_id')
  129. tamper_key = self.get_argument('tamper_key')
  130. message = self.get_argument('message')
  131. try:
  132. self.application.report_error(claim_id, tamper_key, message)
  133. except InvalidClaim:
  134. raise HTTPError(409, reason='Invalid item claimed')
  135. else:
  136. logger.info('Error reported for claim %s', claim_id)
  137. self.write({'status': 'OK'})
  138. class HealthHandler(BaseHandler):
  139. def _show_maintenance_page(self):
  140. self.set_status(512, 'export_in_progress')
  141. def get(self):
  142. status = self.application.get_project_status()
  143. if self.application.is_deadman_safety_tripped():
  144. self.set_status(507, 'deadman_safety_tripped')
  145. self.write({
  146. 'http_status_code': self._status_code,
  147. 'http_status_message': self._reason,
  148. 'git_hash': str(status.git_hash),
  149. 'projects': [project.name for project in status.projects],
  150. 'project_stats': status.project_stats
  151. })