A corporation manager and dashboard for EVE Online
Não pode escolher mais do que 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 
 

168 linhas
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)