A corporation manager and dashboard for EVE Online
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 
 

168 lignes
6.5 KiB

  1. # -*- coding: utf-8 -*-
  2. import sys
  3. import textwrap
  4. from flask import g
  5. from .._provided import config, logger
  6. from ...exceptions import EVEAPIForbiddenError
  7. __all__ = ["update_operation"]
  8. def _save_operation(cname, opname, primary, secondary, key=None):
  9. """Save the given campaign/operation overview info in the database."""
  10. secstr = "" if secondary is None else (" secondary=%d" % secondary)
  11. logger.debug("Setting overview primary=%d%s campaign=%s operation=%s",
  12. primary, secstr, cname, opname)
  13. g.campaign_db.set_overview(cname, opname, primary, secondary)
  14. g.campaign_db.touch_operation(cname, opname, key=key)
  15. def _build_filter(qualifiers, arg):
  16. """Given a qualifiers string from the config, return a filter function.
  17. This function is extremely sensitive since it executes arbitrary Python
  18. code. It should never be run with a user-provided argument! We trust the
  19. contents of a config file because it originates from a known place on the
  20. filesystem.
  21. """
  22. namespace = {"g": g}
  23. body = ("def _func(%s):\n" % arg) + textwrap.indent(qualifiers, " " * 4)
  24. exec(body, namespace)
  25. return namespace["_func"]
  26. def _store_kill(cname, opnames, kill):
  27. """Store the given kill and its associations into the database."""
  28. kid = kill["killID"]
  29. if g.campaign_db.has_kill(kid):
  30. current = g.campaign_db.get_kill_associations(cname, kid)
  31. opnames -= set(current)
  32. if opnames:
  33. logger.debug("Adding operations=%s to kill id=%d campaign=%s",
  34. ",".join(opnames), kid, cname)
  35. else:
  36. logger.debug("Adding kill id=%d campaign=%s operations=%s", kid, cname,
  37. ",".join(opnames))
  38. g.campaign_db.add_kill(kill)
  39. g.campaign_db.associate_kill(cname, kid, opnames)
  40. def _update_killboard_operations(cname, opnames, min_kill_id):
  41. """Update all killboard-type operations in the given campaign subset."""
  42. operations = config["campaigns"][cname]["operations"]
  43. filters = []
  44. for opname in opnames:
  45. qualif = operations[opname]["qualifiers"]
  46. filters.append((_build_filter(qualif, "kill"), opname))
  47. args = ["kills", "corporationID", g.config.get("corp.id"), "no-items",
  48. "no-attackers", "orderDirection", "asc"]
  49. if min_kill_id > 0:
  50. args += ["afterKillID", min_kill_id]
  51. max_kill_id = min_kill_id
  52. for kill in g.eve.zkill.iter_killmails(*args):
  53. kid = kill["killID"]
  54. ktime = kill["killTime"]
  55. logger.debug("Evaluating kill date=%s id=%d for campaign=%s "
  56. "operations=%s", ktime, kid, cname, ",".join(opnames))
  57. max_kill_id = max(max_kill_id, kid)
  58. ops = set()
  59. for filt, opname in filters:
  60. if filt(kill):
  61. ops.add(opname)
  62. if ops:
  63. _store_kill(cname, ops, kill)
  64. for opname in opnames:
  65. primary, secondary = g.campaign_db.count_kills(cname, opname)
  66. show_isk = operations[opname].get("isk", True)
  67. if not show_isk:
  68. secondary = None
  69. _save_operation(cname, opname, primary, secondary, key=max_kill_id)
  70. def _get_prices():
  71. """Return a dict mapping type IDs to ISK prices."""
  72. pricelist = g.eve.esi().v1.markets.prices.get()
  73. return {entry["type_id"]: entry["average_price"]
  74. for entry in pricelist if "average_price" in entry}
  75. def _save_collection_overview(cname, opnames, data):
  76. """Save collection overview data to the database."""
  77. operations = config["campaigns"][cname]["operations"]
  78. for opname in opnames:
  79. primary = sum(count for d in data[opname].values()
  80. for (count, _) in d.values())
  81. show_isk = operations[opname].get("isk", True)
  82. if show_isk:
  83. secondary = sum(value for d in data[opname].values()
  84. for (_, value) in d.values())
  85. else:
  86. secondary = None
  87. _save_operation(cname, opname, primary, secondary)
  88. def _update_collection_operations(cname, opnames):
  89. """Update all collection-type operations in the given campaign subset."""
  90. operations = config["campaigns"][cname]["operations"]
  91. filters = []
  92. for opname in opnames:
  93. qualif = operations[opname]["qualifiers"]
  94. filters.append((_build_filter(qualif, "asset"), opname))
  95. prices = _get_prices()
  96. data = {opname: {} for opname in opnames}
  97. for char_id, token in g.auth.get_valid_characters():
  98. logger.debug("Fetching assets for char id=%d campaign=%s "
  99. "operations=%s", char_id, cname, ",".join(opnames))
  100. try:
  101. assets = g.eve.esi(token).v1.characters(char_id).assets.get()
  102. except EVEAPIForbiddenError:
  103. logger.debug("Asset access denied for char id=%d", char_id)
  104. continue
  105. for opname in opnames:
  106. data[opname][char_id] = {}
  107. logger.debug("Evaluating %d assets for char id=%d",
  108. len(assets), char_id)
  109. for asset in assets:
  110. for filt, opname in filters:
  111. if filt(asset):
  112. typeid = asset["type_id"]
  113. count = 1 if asset["is_singleton"] else asset["quantity"]
  114. value = prices.get(typeid, 0.0)
  115. char = data[opname][char_id]
  116. if typeid in char:
  117. char[typeid][0] += count
  118. char[typeid][1] += count * value
  119. else:
  120. char[typeid] = [count, count * value]
  121. g.campaign_db.update_items(cname, data)
  122. _save_collection_overview(cname, opnames, data)
  123. def update_operation(cname, opname, new=False):
  124. """Update a campaign/operation. Assumes a thread-exclusive lock is held."""
  125. campaign = config["campaigns"][cname]
  126. operations = campaign["operations"]
  127. optype = operations[opname]["type"]
  128. opnames = [opn for opn in campaign["enabled"]
  129. if operations[opn]["type"] == optype]
  130. if optype == "killboard":
  131. opsubset = []
  132. min_key = 0 if new else sys.maxsize
  133. for opname in opnames:
  134. last_updated, key = g.campaign_db.check_operation(cname, opname)
  135. if new and last_updated is None:
  136. opsubset.append(opname)
  137. elif not new and last_updated is not None:
  138. min_key = min(min_key, key)
  139. opsubset.append(opname)
  140. _update_killboard_operations(cname, opsubset, min_key)
  141. elif optype == "collection":
  142. _update_collection_operations(cname, opnames)
  143. else:
  144. raise RuntimeError("Unknown operation type: %s" % optype)