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.
 
 
 
 
 

166 lignes
6.4 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 _save_collection_overview(cname, opnames, data):
  71. """Save collection overview data to the database."""
  72. operations = config["campaigns"][cname]["operations"]
  73. if any(operations[opname].get("isk", True) for opname in opnames):
  74. pricelist = g.eve.esi().v1.markets.prices.get()
  75. prices = {entry["type_id"]: entry["average_price"]
  76. for entry in pricelist if "average_price" in entry}
  77. else:
  78. prices = {}
  79. for opname in opnames:
  80. primary = sum(sum(d.values()) for d in data[opname].values())
  81. show_isk = operations[opname].get("isk", True)
  82. if show_isk:
  83. secondary = sum(prices.get(typeid, 0.0) * count
  84. for d in data[opname].values()
  85. for typeid, count in d.items())
  86. else:
  87. secondary = None
  88. _save_operation(cname, opname, primary, secondary)
  89. def _update_collection_operations(cname, opnames):
  90. """Update all collection-type operations in the given campaign subset."""
  91. operations = config["campaigns"][cname]["operations"]
  92. filters = []
  93. for opname in opnames:
  94. qualif = operations[opname]["qualifiers"]
  95. filters.append((_build_filter(qualif, "asset"), opname))
  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. char = data[opname][char_id]
  115. if typeid in char:
  116. char[typeid] += count
  117. else:
  118. char[typeid] = count
  119. g.campaign_db.update_items(cname, data)
  120. _save_collection_overview(cname, opnames, data)
  121. def update_operation(cname, opname, new=False):
  122. """Update a campaign/operation. Assumes a thread-exclusive lock is held."""
  123. campaign = config["campaigns"][cname]
  124. operations = campaign["operations"]
  125. optype = operations[opname]["type"]
  126. opnames = [opn for opn in campaign["enabled"]
  127. if operations[opn]["type"] == optype]
  128. if optype == "killboard":
  129. opsubset = []
  130. min_key = 0 if new else sys.maxsize
  131. for opname in opnames:
  132. last_updated, key = g.campaign_db.check_operation(cname, opname)
  133. if new and last_updated is None:
  134. opsubset.append(opname)
  135. elif not new and last_updated is not None:
  136. min_key = min(min_key, key)
  137. opsubset.append(opname)
  138. _update_killboard_operations(cname, opsubset, min_key)
  139. elif optype == "collection":
  140. _update_collection_operations(cname, opnames)
  141. else:
  142. raise RuntimeError("Unknown operation type: %s" % optype)