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.
 
 
 
 
 

250 lignes
10 KiB

  1. # -*- coding: utf-8 -*-
  2. from datetime import datetime
  3. import sqlite3
  4. from flask import g
  5. from werkzeug.local import LocalProxy
  6. __all__ = ["CampaignDB"]
  7. class CampaignDB:
  8. """Database manager for internal storage for the Campaigns module."""
  9. path = None
  10. def __init__(self):
  11. if self.path is None:
  12. raise RuntimeError("CampaignDB.path not set")
  13. self._conn = sqlite3.connect(self.path)
  14. @classmethod
  15. def _get(cls):
  16. """Return the current database, or allocate a new one if necessary."""
  17. if not hasattr(g, "_campaign_db"):
  18. g._campaign_db = cls()
  19. return g._campaign_db
  20. @classmethod
  21. def pre_hook(cls):
  22. """Hook to be called before a request context.
  23. Sets up the g.campaign_db proxy.
  24. """
  25. g.campaign_db = LocalProxy(cls._get)
  26. @classmethod
  27. def post_hook(cls, exc):
  28. """Hook to be called when tearing down an application context.
  29. Closes the database if necessary.
  30. """
  31. if hasattr(g, "_campaign_db"):
  32. g._campaign_db.close()
  33. def close(self):
  34. """Close the database connection."""
  35. return self._conn.close()
  36. def check_operation(self, campaign, operation):
  37. """Return the last updated timestamp and key for the given operation.
  38. Return (None, None) if the given operation was never updated.
  39. """
  40. query = """SELECT lu_date, lu_key FROM last_updated
  41. WHERE lu_campaign = ? AND lu_operation = ?"""
  42. res = self._conn.execute(query, (campaign, operation)).fetchall()
  43. if not res:
  44. return None, None
  45. return datetime.strptime(res[0][0], "%Y-%m-%d %H:%M:%S"), res[0][1]
  46. def touch_operation(self, campaign, operation, key=None):
  47. """Mark the given operation as just updated, or add it."""
  48. with self._conn as conn:
  49. cur = conn.execute("BEGIN TRANSACTION")
  50. cur.execute("""UPDATE last_updated
  51. SET lu_date = CURRENT_TIMESTAMP, lu_key = ?
  52. WHERE lu_campaign = ? AND lu_operation = ?""", (
  53. key, campaign, operation))
  54. if cur.rowcount == 0:
  55. cur.execute("""INSERT INTO last_updated
  56. (lu_campaign, lu_operation, lu_key) VALUES (?, ?, ?)""", (
  57. campaign, operation, key))
  58. def set_overview(self, campaign, operation, primary, secondary=None):
  59. """Set overview information for this operation."""
  60. with self._conn as conn:
  61. conn.execute("""INSERT OR REPLACE INTO overview
  62. (ov_campaign, ov_operation, ov_primary, ov_secondary)
  63. VALUES (?, ?, ?, ?)""", (
  64. campaign, operation, primary, secondary))
  65. def get_overview(self, campaign, operation):
  66. """Return a 2-tuple of overview information for this operation."""
  67. query = """SELECT ov_primary, ov_secondary FROM overview
  68. WHERE ov_campaign = ? AND ov_operation = ?"""
  69. res = self._conn.execute(query, (campaign, operation)).fetchall()
  70. return tuple(res[0]) if res else (0, None)
  71. def has_kill(self, kill_id):
  72. """Return whether the database has a killmail with the given ID."""
  73. query = "SELECT 1 FROM kill WHERE kill_id = ?"
  74. res = self._conn.execute(query, (kill_id,)).fetchall()
  75. return bool(res)
  76. def add_kill(self, kill):
  77. """Insert a killmail into the database."""
  78. try:
  79. datetime.strptime(kill["killTime"], "%Y-%m-%d %H:%M:%S")
  80. except ValueError:
  81. raise RuntimeError("Invalid kill_date=%s for kill_id=%d" % (
  82. kill["killTime"], kill["killID"]))
  83. query = """INSERT OR REPLACE INTO kill (
  84. kill_id, kill_date, kill_system, kill_victim_shipid,
  85. kill_victim_charid, kill_victim_charname, kill_victim_corpid,
  86. kill_victim_corpname, kill_victim_allianceid,
  87. kill_victim_alliancename, kill_victim_factionid,
  88. kill_victim_factionname, kill_value)
  89. VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"""
  90. victim = kill["victim"]
  91. args = (
  92. int(kill["killID"]), kill["killTime"], int(kill["solarSystemID"]),
  93. int(victim["shipTypeID"]), int(victim["characterID"]),
  94. victim["characterName"], int(victim["corporationID"]),
  95. victim["corporationName"], int(victim["allianceID"]),
  96. victim["allianceName"], int(victim["factionID"]),
  97. victim["factionName"], float(kill["zkb"]["totalValue"]))
  98. with self._conn as conn:
  99. conn.execute(query, args)
  100. def get_kill_associations(self, campaign, kill_id):
  101. """Return a list of operations associated with a campaign and kill."""
  102. query = """SELECT ok_operation FROM oper_kill
  103. WHERE ok_campaign = ? AND ok_killid = ?"""
  104. res = self._conn.execute(query, (campaign, kill_id)).fetchall()
  105. return [row[0] for row in res]
  106. def associate_kill(self, campaign, kill_id, operations):
  107. """Associate a killmail with a set of campaign/operations."""
  108. query = """INSERT OR IGNORE INTO oper_kill
  109. (ok_campaign, ok_operation, ok_killid) VALUES (?, ?, ?)"""
  110. arglist = [(campaign, op, kill_id) for op in operations]
  111. with self._conn as conn:
  112. conn.executemany(query, arglist)
  113. def count_kills(self, campaign, operation):
  114. """Return the number of matching kills and the total kill value."""
  115. query = """SELECT COUNT(*), TOTAL(kill_value)
  116. FROM oper_kill
  117. JOIN kill ON ok_killid = kill_id
  118. WHERE ok_campaign = ? AND ok_operation = ?"""
  119. res = self._conn.execute(query, (campaign, operation)).fetchall()
  120. return tuple(res[0])
  121. def get_associated_kills(self, campaign, operation, sort="new", limit=-1,
  122. offset=0):
  123. """Return a list of kills associated with a campaign/operation.
  124. Kills are returned as dictionaries, up to a limit. Use -1 for no limit.
  125. The sort should be "new" for most recent first, "old" for most recent
  126. last, or "value" for most valuable first.
  127. """
  128. sortkeys = {
  129. "new": "ok_killid DESC",
  130. "old": "ok_killid ASC",
  131. "value": "kill_value DESC, ok_killid DESC"
  132. }
  133. if sort in sortkeys:
  134. sortkey = sortkeys[sort]
  135. else:
  136. raise ValueError(sort)
  137. if not isinstance(limit, int):
  138. raise ValueError(limit)
  139. if not isinstance(offset, int):
  140. raise ValueError(offset)
  141. query = """SELECT kill_id, kill_date, kill_system, kill_victim_shipid,
  142. kill_victim_charid, kill_victim_charname, kill_victim_corpid,
  143. kill_victim_corpname, kill_victim_allianceid,
  144. kill_victim_alliancename, kill_victim_factionid,
  145. kill_victim_factionname, kill_value
  146. FROM oper_kill
  147. JOIN kill ON ok_killid = kill_id
  148. WHERE ok_campaign = ? AND ok_operation = ?
  149. ORDER BY {} LIMIT {} OFFSET {}"""
  150. qform = query.format(sortkey, limit, offset)
  151. res = self._conn.execute(qform, (campaign, operation)).fetchall()
  152. return [{
  153. "id": row[0],
  154. "date": datetime.strptime(row[1], "%Y-%m-%d %H:%M:%S"),
  155. "system": row[2],
  156. "victim": {
  157. "ship_id": row[3],
  158. "char_id": row[4],
  159. "char_name": row[5],
  160. "corp_id": row[6],
  161. "corp_name": row[7],
  162. "alliance_id": row[8],
  163. "alliance_name": row[9],
  164. "faction_id": row[10],
  165. "faction_name": row[11]
  166. },
  167. "value": row[12]
  168. } for row in res]
  169. def update_items(self, campaign, data):
  170. """Update all item details in the database for the given campaign.
  171. The data should be a multi-layered dictionary. It maps operation names
  172. to a dict that maps character IDs to a dict that maps type IDs to
  173. tuples of integer counts and float values.
  174. """
  175. with self._conn as conn:
  176. cur = conn.execute("BEGIN TRANSACTION")
  177. query = "DELETE FROM oper_item WHERE oi_campaign = ?"
  178. cur.execute(query, (campaign,))
  179. query = """INSERT INTO oper_item (
  180. oi_campaign, oi_operation, oi_character, oi_type, oi_count,
  181. oi_value)
  182. VALUES (?, ?, ?, ?, ?, ?)"""
  183. cur.executemany(query, [
  184. (campaign, operation, int(char_id), int(type_id), int(count),
  185. float(value))
  186. for operation, chars in data.items()
  187. for char_id, types in chars.items()
  188. for type_id, (count, value) in types.items()])
  189. def get_associated_items(self, campaign, operation, sort="value", limit=-1,
  190. offset=0):
  191. """Return a list of items associated with a campaign/operation.
  192. Items are returned as 2-tuples of (item_type, item_count), up to a
  193. limit. Use -1 for no limit. The sort should be "value" for most
  194. valuable first (individual item value * quantity), "quantity" for
  195. greatest quantity first, "price" for most valuable items first.
  196. """
  197. sortkeys = {
  198. "value": "total_value DESC",
  199. "quantity": "total_count DESC",
  200. "price": "(total_value / total_count) DESC"
  201. }
  202. if sort in sortkeys:
  203. sortkey = sortkeys[sort]
  204. else:
  205. raise ValueError(sort)
  206. if not isinstance(limit, int):
  207. raise ValueError(limit)
  208. if not isinstance(offset, int):
  209. raise ValueError(offset)
  210. query = """SELECT oi_type, SUM(oi_count) AS total_count,
  211. TOTAL(oi_value) as total_value
  212. FROM oper_item
  213. WHERE oi_campaign = ? AND oi_operation = ?
  214. GROUP BY oi_type ORDER BY {} LIMIT {} OFFSET {}"""
  215. qform = query.format(sortkey, limit, offset)
  216. res = self._conn.execute(qform, (campaign, operation)).fetchall()
  217. return [(type_id, count or 0, value) for type_id, count, value in res]