Advertisement
Guest User

Untitled

a guest
Nov 14th, 2016
77
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 53.68 KB | None | 0 0
  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. import logging
  4. import itertools
  5. import calendar
  6. import sys
  7. import gc
  8. import time
  9. import geopy
  10. from peewee import SqliteDatabase, InsertQuery, \
  11. IntegerField, CharField, DoubleField, BooleanField, \
  12. DateTimeField, fn, DeleteQuery, CompositeKey, FloatField, SQL, TextField
  13. from playhouse.flask_utils import FlaskDB
  14. from playhouse.pool import PooledMySQLDatabase
  15. from playhouse.shortcuts import RetryOperationalError
  16. from playhouse.migrate import migrate, MySQLMigrator, SqliteMigrator
  17. from datetime import datetime, timedelta
  18. from base64 import b64encode
  19. from cachetools import TTLCache
  20. from cachetools import cached
  21.  
  22. from . import config
  23. from .utils import get_pokemon_name, get_pokemon_rarity, get_pokemon_types, get_args
  24. from .transform import transform_from_wgs_to_gcj, get_new_coords
  25. from .customLog import printPokemon
  26. import luna
  27.  
  28. log = logging.getLogger(__name__)
  29.  
  30. args = get_args()
  31. flaskDb = FlaskDB()
  32. cache = TTLCache(maxsize=100, ttl=60 * 5)
  33.  
  34. db_schema_version = 9
  35.  
  36.  
  37. class MyRetryDB(RetryOperationalError, PooledMySQLDatabase):
  38. pass
  39.  
  40.  
  41. def init_database(app):
  42. if args.db_type == 'mysql':
  43. log.info('Connecting to MySQL database on %s:%i', args.db_host, args.db_port)
  44. connections = args.db_max_connections
  45. if hasattr(args, 'accounts'):
  46. connections *= len(args.accounts)
  47. db = MyRetryDB(
  48. args.db_name,
  49. user=args.db_user,
  50. password=args.db_pass,
  51. host=args.db_host,
  52. port=args.db_port,
  53. max_connections=connections,
  54. stale_timeout=300)
  55. else:
  56. log.info('Connecting to local SQLite database')
  57. db = SqliteDatabase(args.db)
  58.  
  59. app.config['DATABASE'] = db
  60. flaskDb.init_app(app)
  61.  
  62. return db
  63.  
  64.  
  65. class BaseModel(flaskDb.Model):
  66.  
  67. @classmethod
  68. def get_all(cls):
  69. results = [m for m in cls.select().dicts()]
  70. if args.china:
  71. for result in results:
  72. result['latitude'], result['longitude'] = \
  73. transform_from_wgs_to_gcj(
  74. result['latitude'], result['longitude'])
  75. return results
  76.  
  77.  
  78. class Pokemon(BaseModel):
  79. # We are base64 encoding the ids delivered by the api
  80. # because they are too big for sqlite to handle
  81. encounter_id = CharField(primary_key=True, max_length=50)
  82. spawnpoint_id = CharField(index=True)
  83. pokemon_id = IntegerField(index=True)
  84. latitude = DoubleField()
  85. longitude = DoubleField()
  86. disappear_time = DateTimeField(index=True)
  87. individual_attack = IntegerField(null=True)
  88. individual_defense = IntegerField(null=True)
  89. individual_stamina = IntegerField(null=True)
  90. move_1 = IntegerField(null=True)
  91. move_2 = IntegerField(null=True)
  92. last_modified = DateTimeField(null=True, index=True, default=datetime.utcnow)
  93.  
  94. class Meta:
  95. indexes = ((('latitude', 'longitude'), False),)
  96.  
  97. @staticmethod
  98. def get_active(swLat, swLng, neLat, neLng, timestamp=0, oSwLat=None, oSwLng=None, oNeLat=None, oNeLng=None):
  99. query = Pokemon.select()
  100. if not (swLat and swLng and neLat and neLng):
  101. query = (query
  102. .where(Pokemon.disappear_time > datetime.utcnow())
  103. .dicts())
  104. elif timestamp > 0:
  105. # If timestamp is known only load modified pokemon
  106. query = (query
  107. .where(((Pokemon.last_modified > datetime.utcfromtimestamp(timestamp / 1000)) &
  108. (Pokemon.disappear_time > datetime.utcnow())) &
  109. ((Pokemon.latitude >= swLat) &
  110. (Pokemon.longitude >= swLng) &
  111. (Pokemon.latitude <= neLat) &
  112. (Pokemon.longitude <= neLng)))
  113. .dicts())
  114. elif oSwLat and oSwLng and oNeLat and oNeLng:
  115. # Send Pokemon in view but exclude those within old boundaries. Only send newly uncovered Pokemon.
  116. query = (query
  117. .where(((Pokemon.disappear_time > datetime.utcnow()) &
  118. (((Pokemon.latitude >= swLat) &
  119. (Pokemon.longitude >= swLng) &
  120. (Pokemon.latitude <= neLat) &
  121. (Pokemon.longitude <= neLng))) &
  122. ~((Pokemon.disappear_time > datetime.utcnow()) &
  123. (Pokemon.latitude >= oSwLat) &
  124. (Pokemon.longitude >= oSwLng) &
  125. (Pokemon.latitude <= oNeLat) &
  126. (Pokemon.longitude <= oNeLng))))
  127. .dicts())
  128. else:
  129. query = (query
  130. .where((Pokemon.disappear_time > datetime.utcnow()) &
  131. (((Pokemon.latitude >= swLat) &
  132. (Pokemon.longitude >= swLng) &
  133. (Pokemon.latitude <= neLat) &
  134. (Pokemon.longitude <= neLng))))
  135. .dicts())
  136.  
  137. # Performance: Disable the garbage collector prior to creating a (potentially) large dict with append()
  138. gc.disable()
  139.  
  140. pokemons = []
  141. for p in query:
  142. p['pokemon_name'] = get_pokemon_name(p['pokemon_id'])
  143. p['pokemon_rarity'] = get_pokemon_rarity(p['pokemon_id'])
  144. p['pokemon_types'] = get_pokemon_types(p['pokemon_id'])
  145. if args.china:
  146. p['latitude'], p['longitude'] = \
  147. transform_from_wgs_to_gcj(p['latitude'], p['longitude'])
  148. pokemons.append(p)
  149.  
  150. # Re-enable the GC.
  151. gc.enable()
  152.  
  153. return pokemons
  154.  
  155. @staticmethod
  156. def get_active_by_id(ids, swLat, swLng, neLat, neLng):
  157. if not (swLat and swLng and neLat and neLng):
  158. query = (Pokemon
  159. .select()
  160. .where((Pokemon.pokemon_id << ids) &
  161. (Pokemon.disappear_time > datetime.utcnow()))
  162. .dicts())
  163. else:
  164. query = (Pokemon
  165. .select()
  166. .where((Pokemon.pokemon_id << ids) &
  167. (Pokemon.disappear_time > datetime.utcnow()) &
  168. (Pokemon.latitude >= swLat) &
  169. (Pokemon.longitude >= swLng) &
  170. (Pokemon.latitude <= neLat) &
  171. (Pokemon.longitude <= neLng))
  172. .dicts())
  173.  
  174. # Performance: Disable the garbage collector prior to creating a (potentially) large dict with append()
  175. gc.disable()
  176.  
  177. pokemons = []
  178. for p in query:
  179. p['pokemon_name'] = get_pokemon_name(p['pokemon_id'])
  180. p['pokemon_rarity'] = get_pokemon_rarity(p['pokemon_id'])
  181. p['pokemon_types'] = get_pokemon_types(p['pokemon_id'])
  182. if args.china:
  183. p['latitude'], p['longitude'] = \
  184. transform_from_wgs_to_gcj(p['latitude'], p['longitude'])
  185. pokemons.append(p)
  186.  
  187. # Re-enable the GC.
  188. gc.enable()
  189.  
  190. return pokemons
  191.  
  192. @classmethod
  193. @cached(cache)
  194. def get_seen(cls, timediff):
  195. if timediff:
  196. timediff = datetime.utcnow() - timediff
  197. pokemon_count_query = (Pokemon
  198. .select(Pokemon.pokemon_id,
  199. fn.COUNT(Pokemon.pokemon_id).alias('count'),
  200. fn.MAX(Pokemon.disappear_time).alias('lastappeared')
  201. )
  202. .where(Pokemon.disappear_time > timediff)
  203. .group_by(Pokemon.pokemon_id)
  204. .alias('counttable')
  205. )
  206. query = (Pokemon
  207. .select(Pokemon.pokemon_id,
  208. Pokemon.disappear_time,
  209. Pokemon.latitude,
  210. Pokemon.longitude,
  211. pokemon_count_query.c.count)
  212. .join(pokemon_count_query, on=(Pokemon.pokemon_id == pokemon_count_query.c.pokemon_id))
  213. .distinct()
  214. .where(Pokemon.disappear_time == pokemon_count_query.c.lastappeared)
  215. .dicts()
  216. )
  217.  
  218. # Performance: Disable the garbage collector prior to creating a (potentially) large dict with append()
  219. gc.disable()
  220.  
  221. pokemons = []
  222. total = 0
  223. for p in query:
  224. p['pokemon_name'] = get_pokemon_name(p['pokemon_id'])
  225. pokemons.append(p)
  226. total += p['count']
  227.  
  228. # Re-enable the GC.
  229. gc.enable()
  230.  
  231. return {'pokemon': pokemons, 'total': total}
  232.  
  233. @classmethod
  234. def get_appearances(cls, pokemon_id, timediff):
  235. '''
  236. :param pokemon_id: id of pokemon that we need appearances for
  237. :param timediff: limiting period of the selection
  238. :return: list of pokemon appearances over a selected period
  239. '''
  240. if timediff:
  241. timediff = datetime.utcnow() - timediff
  242. query = (Pokemon
  243. .select(Pokemon.latitude, Pokemon.longitude, Pokemon.pokemon_id, fn.Count(Pokemon.spawnpoint_id).alias('count'), Pokemon.spawnpoint_id)
  244. .where((Pokemon.pokemon_id == pokemon_id) &
  245. (Pokemon.disappear_time > timediff)
  246. )
  247. .group_by(Pokemon.latitude, Pokemon.longitude, Pokemon.pokemon_id, Pokemon.spawnpoint_id)
  248. .dicts()
  249. )
  250.  
  251. return list(query)
  252.  
  253. @classmethod
  254. def get_appearances_times_by_spawnpoint(cls, pokemon_id, spawnpoint_id, timediff):
  255. '''
  256. :param pokemon_id: id of pokemon that we need appearances times for
  257. :param spawnpoint_id: spawnpoing id we need appearances times for
  258. :param timediff: limiting period of the selection
  259. :return: list of time appearances over a selected period
  260. '''
  261. if timediff:
  262. timediff = datetime.utcnow() - timediff
  263. query = (Pokemon
  264. .select(Pokemon.disappear_time)
  265. .where((Pokemon.pokemon_id == pokemon_id) &
  266. (Pokemon.spawnpoint_id == spawnpoint_id) &
  267. (Pokemon.disappear_time > timediff)
  268. )
  269. .order_by(Pokemon.disappear_time.asc())
  270. .tuples()
  271. )
  272.  
  273. return list(itertools.chain(*query))
  274.  
  275. @classmethod
  276. def get_spawn_time(cls, disappear_time):
  277. return (disappear_time + 2700) % 3600
  278.  
  279. @classmethod
  280. def get_spawnpoints(cls, swLat, swLng, neLat, neLng, timestamp=0, oSwLat=None, oSwLng=None, oNeLat=None, oNeLng=None):
  281. query = Pokemon.select(Pokemon.latitude, Pokemon.longitude, Pokemon.spawnpoint_id, ((Pokemon.disappear_time.minute * 60) + Pokemon.disappear_time.second).alias('time'), fn.Count(Pokemon.spawnpoint_id).alias('count'))
  282.  
  283. if timestamp > 0:
  284. query = (query
  285. .where(((Pokemon.last_modified > datetime.utcfromtimestamp(timestamp / 1000))) &
  286. ((Pokemon.latitude >= swLat) &
  287. (Pokemon.longitude >= swLng) &
  288. (Pokemon.latitude <= neLat) &
  289. (Pokemon.longitude <= neLng)))
  290. .dicts())
  291. elif oSwLat and oSwLng and oNeLat and oNeLng:
  292. # Send spawnpoints in view but exclude those within old boundaries. Only send newly uncovered spawnpoints.
  293. query = (query
  294. .where((((Pokemon.latitude >= swLat) &
  295. (Pokemon.longitude >= swLng) &
  296. (Pokemon.latitude <= neLat) &
  297. (Pokemon.longitude <= neLng))) &
  298. ~((Pokemon.latitude >= oSwLat) &
  299. (Pokemon.longitude >= oSwLng) &
  300. (Pokemon.latitude <= oNeLat) &
  301. (Pokemon.longitude <= oNeLng)))
  302. .dicts())
  303. elif swLat and swLng and neLat and neLng:
  304. query = (query
  305. .where((Pokemon.latitude <= neLat) &
  306. (Pokemon.latitude >= swLat) &
  307. (Pokemon.longitude >= swLng) &
  308. (Pokemon.longitude <= neLng)
  309. ))
  310.  
  311. query = query.group_by(Pokemon.latitude, Pokemon.longitude, Pokemon.spawnpoint_id, SQL('time'))
  312.  
  313. queryDict = query.dicts()
  314. spawnpoints = {}
  315.  
  316. for sp in queryDict:
  317. key = sp['spawnpoint_id']
  318. disappear_time = cls.get_spawn_time(sp.pop('time'))
  319. count = int(sp['count'])
  320.  
  321. if key not in spawnpoints:
  322. spawnpoints[key] = sp
  323. else:
  324. spawnpoints[key]['special'] = True
  325.  
  326. if 'time' not in spawnpoints[key] or count >= spawnpoints[key]['count']:
  327. spawnpoints[key]['time'] = disappear_time
  328. spawnpoints[key]['count'] = count
  329.  
  330. for sp in spawnpoints.values():
  331. del sp['count']
  332.  
  333. return list(spawnpoints.values())
  334.  
  335. @classmethod
  336. def get_spawnpoints_in_hex(cls, center, steps):
  337. log.info('Finding spawn points {} steps away'.format(steps))
  338.  
  339. n, e, s, w = hex_bounds(center, steps)
  340.  
  341. query = (Pokemon
  342. .select(Pokemon.latitude.alias('lat'),
  343. Pokemon.longitude.alias('lng'),
  344. ((Pokemon.disappear_time.minute * 60) + Pokemon.disappear_time.second).alias('time'),
  345. Pokemon.spawnpoint_id
  346. ))
  347. query = (query.where((Pokemon.latitude <= n) &
  348. (Pokemon.latitude >= s) &
  349. (Pokemon.longitude >= w) &
  350. (Pokemon.longitude <= e)
  351. ))
  352. # Sqlite doesn't support distinct on columns
  353. if args.db_type == 'mysql':
  354. query = query.distinct(Pokemon.spawnpoint_id)
  355. else:
  356. query = query.group_by(Pokemon.spawnpoint_id)
  357.  
  358. s = list(query.dicts())
  359.  
  360. # The distance between scan circles of radius 70 in a hex is 121.2436
  361. # steps - 1 to account for the center circle then add 70 for the edge
  362. step_distance = ((steps - 1) * 121.2436) + 70
  363. # Compare spawnpoint list to a circle with radius steps * 120
  364. # Uses the direct geopy distance between the center and the spawnpoint.
  365. filtered = []
  366.  
  367. for idx, sp in enumerate(s):
  368. if geopy.distance.distance(center, (sp['lat'], sp['lng'])).meters <= step_distance:
  369. filtered.append(s[idx])
  370.  
  371. # at this point, 'time' is DISAPPEARANCE time, we're going to morph it to APPEARANCE time
  372. for location in filtered:
  373. # examples: time shifted
  374. # 0 ( 0 + 2700) = 2700 % 3600 = 2700 (0th minute to 45th minute, 15 minutes prior to appearance as time wraps around the hour)
  375. # 1800 (1800 + 2700) = 4500 % 3600 = 900 (30th minute, moved to arrive at 15th minute)
  376. # todo: this DOES NOT ACCOUNT for pokemons that appear sooner and live longer, but you'll _always_ have at least 15 minutes, so it works well enough
  377. location['time'] = cls.get_spawn_time(location['time'])
  378.  
  379. return filtered
  380.  
  381.  
  382. class Pokestop(BaseModel):
  383. pokestop_id = CharField(primary_key=True, max_length=50)
  384. enabled = BooleanField()
  385. latitude = DoubleField()
  386. longitude = DoubleField()
  387. last_modified = DateTimeField(index=True)
  388. lure_expiration = DateTimeField(null=True, index=True)
  389. active_fort_modifier = CharField(max_length=50, null=True)
  390. last_updated = DateTimeField(null=True, index=True, default=datetime.utcnow)
  391.  
  392. class Meta:
  393. indexes = ((('latitude', 'longitude'), False),)
  394.  
  395. @staticmethod
  396. def get_stops(swLat, swLng, neLat, neLng, timestamp=0, oSwLat=None, oSwLng=None, oNeLat=None, oNeLng=None, lured=False):
  397.  
  398. query = Pokestop.select(Pokestop.active_fort_modifier, Pokestop.enabled, Pokestop.latitude, Pokestop.longitude, Pokestop.last_modified, Pokestop.lure_expiration, Pokestop.pokestop_id)
  399.  
  400. if not (swLat and swLng and neLat and neLng):
  401. query = (query
  402. .dicts())
  403. elif timestamp > 0:
  404. query = (query
  405. .where(((Pokestop.last_updated > datetime.utcfromtimestamp(timestamp / 1000))) &
  406. (Pokestop.latitude >= swLat) &
  407. (Pokestop.longitude >= swLng) &
  408. (Pokestop.latitude <= neLat) &
  409. (Pokestop.longitude <= neLng))
  410. .dicts())
  411. elif oSwLat and oSwLng and oNeLat and oNeLng and lured:
  412. query = (query
  413. .where((((Pokestop.latitude >= swLat) &
  414. (Pokestop.longitude >= swLng) &
  415. (Pokestop.latitude <= neLat) &
  416. (Pokestop.longitude <= neLng)) &
  417. (Pokestop.active_fort_modifier.is_null(False))) &
  418. ~((Pokestop.latitude >= oSwLat) &
  419. (Pokestop.longitude >= oSwLng) &
  420. (Pokestop.latitude <= oNeLat) &
  421. (Pokestop.longitude <= oNeLng)) &
  422. (Pokestop.active_fort_modifier.is_null(False)))
  423. .dicts())
  424. elif oSwLat and oSwLng and oNeLat and oNeLng:
  425. # Send stops in view but exclude those within old boundaries. Only send newly uncovered stops.
  426. query = (query
  427. .where(((Pokestop.latitude >= swLat) &
  428. (Pokestop.longitude >= swLng) &
  429. (Pokestop.latitude <= neLat) &
  430. (Pokestop.longitude <= neLng)) &
  431. ~((Pokestop.latitude >= oSwLat) &
  432. (Pokestop.longitude >= oSwLng) &
  433. (Pokestop.latitude <= oNeLat) &
  434. (Pokestop.longitude <= oNeLng)))
  435. .dicts())
  436. elif lured:
  437. query = (query
  438. .where(((Pokestop.last_updated > datetime.utcfromtimestamp(timestamp / 1000))) &
  439. ((Pokestop.latitude >= swLat) &
  440. (Pokestop.longitude >= swLng) &
  441. (Pokestop.latitude <= neLat) &
  442. (Pokestop.longitude <= neLng)) &
  443. (Pokestop.active_fort_modifier.is_null(False)))
  444. .dicts())
  445.  
  446. else:
  447. query = (query
  448. .where((Pokestop.latitude >= swLat) &
  449. (Pokestop.longitude >= swLng) &
  450. (Pokestop.latitude <= neLat) &
  451. (Pokestop.longitude <= neLng))
  452. .dicts())
  453.  
  454. # Performance: Disable the garbage collector prior to creating a (potentially) large dict with append()
  455. gc.disable()
  456.  
  457. pokestops = []
  458. for p in query:
  459. if args.china:
  460. p['latitude'], p['longitude'] = \
  461. transform_from_wgs_to_gcj(p['latitude'], p['longitude'])
  462. pokestops.append(p)
  463.  
  464. # Re-enable the GC.
  465. gc.enable()
  466.  
  467. return pokestops
  468.  
  469.  
  470. class Gym(BaseModel):
  471. UNCONTESTED = 0
  472. TEAM_MYSTIC = 1
  473. TEAM_VALOR = 2
  474. TEAM_INSTINCT = 3
  475.  
  476. gym_id = CharField(primary_key=True, max_length=50)
  477. team_id = IntegerField()
  478. guard_pokemon_id = IntegerField()
  479. gym_points = IntegerField()
  480. enabled = BooleanField()
  481. latitude = DoubleField()
  482. longitude = DoubleField()
  483. last_modified = DateTimeField(index=True)
  484. last_scanned = DateTimeField(default=datetime.utcnow)
  485.  
  486. class Meta:
  487. indexes = ((('latitude', 'longitude'), False),)
  488.  
  489. @staticmethod
  490. def get_gyms(swLat, swLng, neLat, neLng, timestamp=0, oSwLat=None, oSwLng=None, oNeLat=None, oNeLng=None):
  491. if not (swLat and swLng and neLat and neLng):
  492. results = (Gym
  493. .select()
  494. .dicts())
  495. elif timestamp > 0:
  496. # If timestamp is known only send last scanned Gyms.
  497. results = (Gym
  498. .select()
  499. .where(((Gym.last_scanned > datetime.utcfromtimestamp(timestamp / 1000)) &
  500. (Gym.latitude >= swLat) &
  501. (Gym.longitude >= swLng) &
  502. (Gym.latitude <= neLat) &
  503. (Gym.longitude <= neLng)))
  504. .dicts())
  505. elif oSwLat and oSwLng and oNeLat and oNeLng:
  506. # Send gyms in view but exclude those within old boundaries. Only send newly uncovered gyms.
  507. results = (Gym
  508. .select()
  509. .where(((Gym.latitude >= swLat) &
  510. (Gym.longitude >= swLng) &
  511. (Gym.latitude <= neLat) &
  512. (Gym.longitude <= neLng)) &
  513. ~((Gym.latitude >= oSwLat) &
  514. (Gym.longitude >= oSwLng) &
  515. (Gym.latitude <= oNeLat) &
  516. (Gym.longitude <= oNeLng)))
  517. .dicts())
  518.  
  519. else:
  520. results = (Gym
  521. .select()
  522. .where((Gym.latitude >= swLat) &
  523. (Gym.longitude >= swLng) &
  524. (Gym.latitude <= neLat) &
  525. (Gym.longitude <= neLng))
  526. .dicts())
  527.  
  528. # Performance: Disable the garbage collector prior to creating a (potentially) large dict with append()
  529. gc.disable()
  530.  
  531. gyms = {}
  532. gym_ids = []
  533. for g in results:
  534. g['name'] = None
  535. g['pokemon'] = []
  536. gyms[g['gym_id']] = g
  537. gym_ids.append(g['gym_id'])
  538.  
  539. if len(gym_ids) > 0:
  540. pokemon = (GymMember
  541. .select(
  542. GymMember.gym_id,
  543. GymPokemon.cp.alias('pokemon_cp'),
  544. GymPokemon.pokemon_id,
  545. Trainer.name.alias('trainer_name'),
  546. Trainer.level.alias('trainer_level'))
  547. .join(Gym, on=(GymMember.gym_id == Gym.gym_id))
  548. .join(GymPokemon, on=(GymMember.pokemon_uid == GymPokemon.pokemon_uid))
  549. .join(Trainer, on=(GymPokemon.trainer_name == Trainer.name))
  550. .where(GymMember.gym_id << gym_ids)
  551. .where(GymMember.last_scanned > Gym.last_modified)
  552. .order_by(GymMember.gym_id, GymPokemon.cp)
  553. .dicts())
  554.  
  555. for p in pokemon:
  556. p['pokemon_name'] = get_pokemon_name(p['pokemon_id'])
  557. gyms[p['gym_id']]['pokemon'].append(p)
  558.  
  559. details = (GymDetails
  560. .select(
  561. GymDetails.gym_id,
  562. GymDetails.name)
  563. .where(GymDetails.gym_id << gym_ids)
  564. .dicts())
  565.  
  566. for d in details:
  567. gyms[d['gym_id']]['name'] = d['name']
  568.  
  569. # Re-enable the GC.
  570. gc.enable()
  571.  
  572. return gyms
  573.  
  574.  
  575. class ScannedLocation(BaseModel):
  576. latitude = DoubleField()
  577. longitude = DoubleField()
  578. last_modified = DateTimeField(index=True, default=datetime.utcnow)
  579.  
  580. class Meta:
  581. primary_key = CompositeKey('latitude', 'longitude')
  582.  
  583. @staticmethod
  584. def get_recent(swLat, swLng, neLat, neLng, timestamp=0, oSwLat=None, oSwLng=None, oNeLat=None, oNeLng=None):
  585. activeTime = (datetime.utcnow() - timedelta(minutes=15))
  586. if timestamp > 0:
  587. query = (ScannedLocation
  588. .select()
  589. .where(((ScannedLocation.last_modified >= datetime.utcfromtimestamp(timestamp / 1000))) &
  590. (ScannedLocation.latitude >= swLat) &
  591. (ScannedLocation.longitude >= swLng) &
  592. (ScannedLocation.latitude <= neLat) &
  593. (ScannedLocation.longitude <= neLng))
  594. .dicts())
  595. elif oSwLat and oSwLng and oNeLat and oNeLng:
  596. # Send scannedlocations in view but exclude those within old boundaries. Only send newly uncovered scannedlocations.
  597. query = (ScannedLocation
  598. .select()
  599. .where((((ScannedLocation.last_modified >= activeTime)) &
  600. (ScannedLocation.latitude >= swLat) &
  601. (ScannedLocation.longitude >= swLng) &
  602. (ScannedLocation.latitude <= neLat) &
  603. (ScannedLocation.longitude <= neLng)) &
  604. ~(((ScannedLocation.last_modified >= activeTime)) &
  605. (ScannedLocation.latitude >= oSwLat) &
  606. (ScannedLocation.longitude >= oSwLng) &
  607. (ScannedLocation.latitude <= oNeLat) &
  608. (ScannedLocation.longitude <= oNeLng)))
  609. .dicts())
  610. else:
  611. query = (ScannedLocation
  612. .select()
  613. .where((ScannedLocation.last_modified >= activeTime) &
  614. (ScannedLocation.latitude >= swLat) &
  615. (ScannedLocation.longitude >= swLng) &
  616. (ScannedLocation.latitude <= neLat) &
  617. (ScannedLocation.longitude <= neLng))
  618. .order_by(ScannedLocation.last_modified.asc())
  619. .dicts())
  620.  
  621. return list(query)
  622.  
  623.  
  624. class MainWorker(BaseModel):
  625. worker_name = CharField(primary_key=True, max_length=50)
  626. message = CharField()
  627. method = CharField(max_length=50)
  628. last_modified = DateTimeField(index=True)
  629.  
  630.  
  631. class WorkerStatus(BaseModel):
  632. username = CharField(primary_key=True, max_length=50)
  633. worker_name = CharField()
  634. success = IntegerField()
  635. fail = IntegerField()
  636. no_items = IntegerField()
  637. skip = IntegerField()
  638. last_modified = DateTimeField(index=True)
  639. message = CharField(max_length=255)
  640.  
  641. @staticmethod
  642. def get_recent():
  643. query = (WorkerStatus
  644. .select()
  645. .where((WorkerStatus.last_modified >=
  646. (datetime.utcnow() - timedelta(minutes=5))))
  647. .order_by(WorkerStatus.username)
  648. .dicts())
  649.  
  650. status = []
  651. for s in query:
  652. status.append(s)
  653.  
  654. return status
  655.  
  656.  
  657. class Versions(flaskDb.Model):
  658. key = CharField()
  659. val = IntegerField()
  660.  
  661. class Meta:
  662. primary_key = False
  663.  
  664.  
  665. class GymMember(BaseModel):
  666. gym_id = CharField(index=True)
  667. pokemon_uid = CharField()
  668. last_scanned = DateTimeField(default=datetime.utcnow)
  669.  
  670. class Meta:
  671. primary_key = False
  672.  
  673.  
  674. class GymPokemon(BaseModel):
  675. pokemon_uid = CharField(primary_key=True, max_length=50)
  676. pokemon_id = IntegerField()
  677. cp = IntegerField()
  678. trainer_name = CharField()
  679. num_upgrades = IntegerField(null=True)
  680. move_1 = IntegerField(null=True)
  681. move_2 = IntegerField(null=True)
  682. height = FloatField(null=True)
  683. weight = FloatField(null=True)
  684. stamina = IntegerField(null=True)
  685. stamina_max = IntegerField(null=True)
  686. cp_multiplier = FloatField(null=True)
  687. additional_cp_multiplier = FloatField(null=True)
  688. iv_defense = IntegerField(null=True)
  689. iv_stamina = IntegerField(null=True)
  690. iv_attack = IntegerField(null=True)
  691. last_seen = DateTimeField(default=datetime.utcnow)
  692.  
  693.  
  694. class Trainer(BaseModel):
  695. name = CharField(primary_key=True, max_length=50)
  696. team = IntegerField()
  697. level = IntegerField()
  698. last_seen = DateTimeField(default=datetime.utcnow)
  699.  
  700.  
  701. class GymDetails(BaseModel):
  702. gym_id = CharField(primary_key=True, max_length=50)
  703. name = CharField()
  704. description = TextField(null=True, default="")
  705. url = CharField()
  706. last_scanned = DateTimeField(default=datetime.utcnow)
  707.  
  708.  
  709. def hex_bounds(center, steps):
  710. # Make a box that is (70m * step_limit * 2) + 70m away from the center point
  711. # Rationale is that you need to travel
  712. sp_dist = 0.07 * 2 * steps
  713. n = get_new_coords(center, sp_dist, 0)[0]
  714. e = get_new_coords(center, sp_dist, 90)[1]
  715. s = get_new_coords(center, sp_dist, 180)[0]
  716. w = get_new_coords(center, sp_dist, 270)[1]
  717. return (n, e, s, w)
  718.  
  719.  
  720. def construct_pokemon_dict(pokemons, p, encounter_result, d_t):
  721. pokemons[p['encounter_id']] = {
  722. 'encounter_id': b64encode(str(p['encounter_id'])),
  723. 'spawnpoint_id': p['spawn_point_id'],
  724. 'pokemon_id': p['pokemon_data']['pokemon_id'],
  725. 'latitude': p['latitude'],
  726. 'longitude': p['longitude'],
  727. 'disappear_time': d_t,
  728. }
  729. if encounter_result is not None and 'wild_pokemon' in encounter_result['responses']['ENCOUNTER']:
  730. pokemon_info = encounter_result['responses']['ENCOUNTER']['wild_pokemon']['pokemon_data']
  731. attack = pokemon_info.get('individual_attack', 0)
  732. defense = pokemon_info.get('individual_defense', 0)
  733. stamina = pokemon_info.get('individual_stamina', 0)
  734. pokemons[p['encounter_id']].update({
  735. 'individual_attack': attack,
  736. 'individual_defense': defense,
  737. 'individual_stamina': stamina,
  738. 'move_1': pokemon_info['move_1'],
  739. 'move_2': pokemon_info['move_2'],
  740. })
  741. else:
  742. if encounter_result is not None and 'wild_pokemon' not in encounter_result['responses']['ENCOUNTER']:
  743. log.warning("Error encountering {}, status code: {}".format(p['encounter_id'], encounter_result['responses']['ENCOUNTER']['status']))
  744. pokemons[p['encounter_id']].update({
  745. 'individual_attack': None,
  746. 'individual_defense': None,
  747. 'individual_stamina': None,
  748. 'move_1': None,
  749. 'move_2': None,
  750. })
  751.  
  752.  
  753. # todo: this probably shouldn't _really_ be in "models" anymore, but w/e
  754. def parse_map(args, map_dict, step_location, db_update_queue, wh_update_queue, api):
  755. pokemons = {}
  756. pokestops = {}
  757. gyms = {}
  758. skipped = 0
  759. stopsskipped = 0
  760. forts = None
  761. wild_pokemon = None
  762. pokesfound = False
  763. fortsfound = False
  764.  
  765. cells = map_dict['responses']['GET_MAP_OBJECTS']['map_cells']
  766. for cell in cells:
  767. if config['parse_pokemon']:
  768. if len(cell.get('wild_pokemons', [])) > 0:
  769. pokesfound = True
  770. if wild_pokemon is None:
  771. wild_pokemon = cell.get('wild_pokemons', [])
  772. else:
  773. wild_pokemon += cell.get('wild_pokemons', [])
  774.  
  775. if config['parse_pokestops'] or config['parse_gyms']:
  776. if len(cell.get('forts', [])) > 0:
  777. fortsfound = True
  778. if forts is None:
  779. forts = cell.get('forts', [])
  780. else:
  781. forts += cell.get('forts', [])
  782.  
  783. if pokesfound:
  784. encounter_ids = [b64encode(str(p['encounter_id'])) for p in wild_pokemon]
  785. # For all the wild pokemon we found check if an active pokemon is in the database
  786. query = (Pokemon
  787. .select(Pokemon.encounter_id, Pokemon.spawnpoint_id)
  788. .where((Pokemon.disappear_time > datetime.utcnow()) & (Pokemon.encounter_id << encounter_ids))
  789. .dicts())
  790.  
  791. # Store all encounter_ids and spawnpoint_id for the pokemon in query (all thats needed to make sure its unique)
  792. encountered_pokemon = [(p['encounter_id'], p['spawnpoint_id']) for p in query]
  793.  
  794. for p in wild_pokemon:
  795. if (b64encode(str(p['encounter_id'])), p['spawn_point_id']) in encountered_pokemon:
  796. # If pokemon has been encountered before dont process it.
  797. skipped += 1
  798. continue
  799.  
  800. # time_till_hidden_ms was overflowing causing a negative integer.
  801. # It was also returning a value above 3.6M ms.
  802. if 0 < p['time_till_hidden_ms'] < 3600000:
  803. d_t = datetime.utcfromtimestamp(
  804. (p['last_modified_timestamp_ms'] +
  805. p['time_till_hidden_ms']) / 1000.0)
  806. else:
  807. # Set a value of 15 minutes because currently its unknown but larger than 15.
  808. d_t = datetime.utcfromtimestamp((p['last_modified_timestamp_ms'] + 900000) / 1000.0)
  809.  
  810. printPokemon(p['pokemon_data']['pokemon_id'], p['latitude'],
  811. p['longitude'], d_t)
  812.  
  813. # Scan for IVs and moves
  814. encounter_result = None
  815. if (args.encounter and (p['pokemon_data']['pokemon_id'] in args.encounter_whitelist or
  816. p['pokemon_data']['pokemon_id'] not in args.encounter_blacklist and not args.encounter_whitelist)):
  817. time.sleep(args.encounter_delay)
  818. encounter_result = api.encounter(encounter_id=p['encounter_id'],
  819. spawn_point_id=p['spawn_point_id'],
  820. player_latitude=step_location[0],
  821. player_longitude=step_location[1])
  822. construct_pokemon_dict(pokemons, p, encounter_result, d_t)
  823. if (luna.checkNotify(p) && luna.filterIV(0.2, pokemons[p['encounter_id']]):
  824. if not poke_found(pokemons[p['encounter_id']]['encounter_id']):
  825. luna.pokeNotify(p,pokemons[p['encounter_id']]['encounter_id'])
  826. else:
  827. log.info('Skipping notification for {}, already in database.'.format(p['pokemon_data']['pokemon_id']))
  828. if args.webhooks:
  829. wh_update_queue.put(('pokemon', {
  830. 'encounter_id': b64encode(str(p['encounter_id'])),
  831. 'spawnpoint_id': p['spawn_point_id'],
  832. 'pokemon_id': p['pokemon_data']['pokemon_id'],
  833. 'latitude': p['latitude'],
  834. 'longitude': p['longitude'],
  835. 'disappear_time': calendar.timegm(d_t.timetuple()),
  836. 'last_modified_time': p['last_modified_timestamp_ms'],
  837. 'time_until_hidden_ms': p['time_till_hidden_ms'],
  838. 'individual_attack': pokemons[p['encounter_id']]['individual_attack'],
  839. 'individual_defense': pokemons[p['encounter_id']]['individual_defense'],
  840. 'individual_stamina': pokemons[p['encounter_id']]['individual_stamina'],
  841. 'move_1': pokemons[p['encounter_id']]['move_1'],
  842. 'move_2': pokemons[p['encounter_id']]['move_2']
  843. }))
  844.  
  845. if fortsfound:
  846. if config['parse_pokestops']:
  847. stop_ids = [f['id'] for f in forts if f.get('type') == 1]
  848. if len(stop_ids) > 0:
  849. query = (Pokestop
  850. .select(Pokestop.pokestop_id, Pokestop.last_modified)
  851. .where((Pokestop.pokestop_id << stop_ids))
  852. .dicts())
  853. encountered_pokestops = [(f['pokestop_id'], int((f['last_modified'] - datetime(1970, 1, 1)).total_seconds())) for f in query]
  854.  
  855. for f in forts:
  856. if config['parse_pokestops'] and f.get('type') == 1: # Pokestops
  857. if 'active_fort_modifier' in f:
  858. lure_expiration = datetime.utcfromtimestamp(
  859. f['last_modified_timestamp_ms'] / 1000.0) + timedelta(minutes=30)
  860. active_fort_modifier = f['active_fort_modifier']
  861. if args.webhooks and args.webhook_updates_only:
  862. wh_update_queue.put(('pokestop', {
  863. 'pokestop_id': b64encode(str(f['id'])),
  864. 'enabled': f['enabled'],
  865. 'latitude': f['latitude'],
  866. 'longitude': f['longitude'],
  867. 'last_modified_time': f['last_modified_timestamp_ms'],
  868. 'lure_expiration': calendar.timegm(lure_expiration.timetuple()),
  869. 'active_fort_modifier': active_fort_modifier
  870. }))
  871. else:
  872. lure_expiration, active_fort_modifier = None, None
  873.  
  874. # Send all pokéstops to webhooks
  875. if args.webhooks and not args.webhook_updates_only:
  876. # Explicitly set 'webhook_data', in case we want to change the information pushed to webhooks,
  877. # similar to above and previous commits.
  878. l_e = None
  879.  
  880. if lure_expiration is not None:
  881. l_e = calendar.timegm(lure_expiration.timetuple())
  882.  
  883. wh_update_queue.put(('pokestop', {
  884. 'pokestop_id': b64encode(str(f['id'])),
  885. 'enabled': f['enabled'],
  886. 'latitude': f['latitude'],
  887. 'longitude': f['longitude'],
  888. 'last_modified': f['last_modified_timestamp_ms'],
  889. 'lure_expiration': l_e,
  890. 'active_fort_modifier': active_fort_modifier
  891. }))
  892.  
  893. if (f['id'], int(f['last_modified_timestamp_ms'] / 1000.0)) in encountered_pokestops:
  894. # If pokestop has been encountered before and hasn't changed dont process it.
  895. stopsskipped += 1
  896. continue
  897.  
  898. pokestops[f['id']] = {
  899. 'pokestop_id': f['id'],
  900. 'enabled': f['enabled'],
  901. 'latitude': f['latitude'],
  902. 'longitude': f['longitude'],
  903. 'last_modified': datetime.utcfromtimestamp(
  904. f['last_modified_timestamp_ms'] / 1000.0),
  905. 'lure_expiration': lure_expiration,
  906. 'active_fort_modifier': active_fort_modifier
  907. }
  908.  
  909. elif config['parse_gyms'] and f.get('type') is None: # Currently, there are only stops and gyms
  910. # Send gyms to webhooks
  911. if args.webhooks and not args.webhook_updates_only:
  912. # Explicitly set 'webhook_data', in case we want to change the information pushed to webhooks,
  913. # similar to above and previous commits.
  914. wh_update_queue.put(('gym', {
  915. 'gym_id': b64encode(str(f['id'])),
  916. 'team_id': f.get('owned_by_team', 0),
  917. 'guard_pokemon_id': f.get('guard_pokemon_id', 0),
  918. 'gym_points': f.get('gym_points', 0),
  919. 'enabled': f['enabled'],
  920. 'latitude': f['latitude'],
  921. 'longitude': f['longitude'],
  922. 'last_modified': f['last_modified_timestamp_ms']
  923. }))
  924.  
  925. gyms[f['id']] = {
  926. 'gym_id': f['id'],
  927. 'team_id': f.get('owned_by_team', 0),
  928. 'guard_pokemon_id': f.get('guard_pokemon_id', 0),
  929. 'gym_points': f.get('gym_points', 0),
  930. 'enabled': f['enabled'],
  931. 'latitude': f['latitude'],
  932. 'longitude': f['longitude'],
  933. 'last_modified': datetime.utcfromtimestamp(
  934. f['last_modified_timestamp_ms'] / 1000.0),
  935. }
  936.  
  937. if len(pokemons):
  938. db_update_queue.put((Pokemon, pokemons))
  939. if len(pokestops):
  940. db_update_queue.put((Pokestop, pokestops))
  941. if len(gyms):
  942. db_update_queue.put((Gym, gyms))
  943.  
  944. log.info('Parsing found %d pokemons, %d pokestops, and %d gyms.',
  945. len(pokemons) + skipped,
  946. len(pokestops) + stopsskipped,
  947. len(gyms))
  948.  
  949. log.debug('Skipped %d Pokemons and %d pokestops.',
  950. skipped,
  951. stopsskipped)
  952.  
  953. db_update_queue.put((ScannedLocation, {0: {
  954. 'latitude': step_location[0],
  955. 'longitude': step_location[1],
  956. }}))
  957.  
  958. return {
  959. 'count': skipped + stopsskipped + len(pokemons) + len(pokestops) + len(gyms),
  960. 'gyms': gyms,
  961. }
  962.  
  963.  
  964. def parse_gyms(args, gym_responses, wh_update_queue):
  965. gym_details = {}
  966. gym_members = {}
  967. gym_pokemon = {}
  968. trainers = {}
  969.  
  970. i = 0
  971. for g in gym_responses.values():
  972. gym_state = g['gym_state']
  973. gym_id = gym_state['fort_data']['id']
  974.  
  975. gym_details[gym_id] = {
  976. 'gym_id': gym_id,
  977. 'name': g['name'],
  978. 'description': g.get('description'),
  979. 'url': g['urls'][0],
  980. }
  981.  
  982. if args.webhooks:
  983. webhook_data = {
  984. 'id': gym_id,
  985. 'latitude': gym_state['fort_data']['latitude'],
  986. 'longitude': gym_state['fort_data']['longitude'],
  987. 'team': gym_state['fort_data'].get('owned_by_team', 0),
  988. 'name': g['name'],
  989. 'description': g.get('description'),
  990. 'url': g['urls'][0],
  991. 'pokemon': [],
  992. }
  993.  
  994. for member in gym_state.get('memberships', []):
  995. gym_members[i] = {
  996. 'gym_id': gym_id,
  997. 'pokemon_uid': member['pokemon_data']['id'],
  998. }
  999.  
  1000. gym_pokemon[i] = {
  1001. 'pokemon_uid': member['pokemon_data']['id'],
  1002. 'pokemon_id': member['pokemon_data']['pokemon_id'],
  1003. 'cp': member['pokemon_data']['cp'],
  1004. 'trainer_name': member['trainer_public_profile']['name'],
  1005. 'num_upgrades': member['pokemon_data'].get('num_upgrades', 0),
  1006. 'move_1': member['pokemon_data'].get('move_1'),
  1007. 'move_2': member['pokemon_data'].get('move_2'),
  1008. 'height': member['pokemon_data'].get('height_m'),
  1009. 'weight': member['pokemon_data'].get('weight_kg'),
  1010. 'stamina': member['pokemon_data'].get('stamina'),
  1011. 'stamina_max': member['pokemon_data'].get('stamina_max'),
  1012. 'cp_multiplier': member['pokemon_data'].get('cp_multiplier'),
  1013. 'additional_cp_multiplier': member['pokemon_data'].get('additional_cp_multiplier', 0),
  1014. 'iv_defense': member['pokemon_data'].get('individual_defense', 0),
  1015. 'iv_stamina': member['pokemon_data'].get('individual_stamina', 0),
  1016. 'iv_attack': member['pokemon_data'].get('individual_attack', 0),
  1017. 'last_seen': datetime.utcnow(),
  1018. }
  1019.  
  1020. trainers[i] = {
  1021. 'name': member['trainer_public_profile']['name'],
  1022. 'team': gym_state['fort_data']['owned_by_team'],
  1023. 'level': member['trainer_public_profile']['level'],
  1024. 'last_seen': datetime.utcnow(),
  1025. }
  1026.  
  1027. if args.webhooks:
  1028. webhook_data['pokemon'].append({
  1029. 'pokemon_uid': member['pokemon_data']['id'],
  1030. 'pokemon_id': member['pokemon_data']['pokemon_id'],
  1031. 'cp': member['pokemon_data']['cp'],
  1032. 'num_upgrades': member['pokemon_data'].get('num_upgrades', 0),
  1033. 'move_1': member['pokemon_data'].get('move_1'),
  1034. 'move_2': member['pokemon_data'].get('move_2'),
  1035. 'height': member['pokemon_data'].get('height_m'),
  1036. 'weight': member['pokemon_data'].get('weight_kg'),
  1037. 'stamina': member['pokemon_data'].get('stamina'),
  1038. 'stamina_max': member['pokemon_data'].get('stamina_max'),
  1039. 'cp_multiplier': member['pokemon_data'].get('cp_multiplier'),
  1040. 'additional_cp_multiplier': member['pokemon_data'].get('additional_cp_multiplier', 0),
  1041. 'iv_defense': member['pokemon_data'].get('individual_defense', 0),
  1042. 'iv_stamina': member['pokemon_data'].get('individual_stamina', 0),
  1043. 'iv_attack': member['pokemon_data'].get('individual_attack', 0),
  1044. 'trainer_name': member['trainer_public_profile']['name'],
  1045. 'trainer_level': member['trainer_public_profile']['level'],
  1046. })
  1047.  
  1048. i += 1
  1049. if args.webhooks:
  1050. wh_update_queue.put(('gym_details', webhook_data))
  1051.  
  1052. # All this database stuff is synchronous (not using the upsert queue) on purpose.
  1053. # Since the search workers load the GymDetails model from the database to determine if a gym
  1054. # needs rescanned, we need to be sure the GymDetails get fully committed to the database before moving on.
  1055. #
  1056. # We _could_ synchronously upsert GymDetails, then queue the other tables for
  1057. # upsert, but that would put that Gym's overall information in a weird non-atomic state.
  1058.  
  1059. # upsert all the models
  1060. if len(gym_details):
  1061. bulk_upsert(GymDetails, gym_details)
  1062. if len(gym_pokemon):
  1063. bulk_upsert(GymPokemon, gym_pokemon)
  1064. if len(trainers):
  1065. bulk_upsert(Trainer, trainers)
  1066.  
  1067. # This needs to be completed in a transaction, because we don't wany any other thread or process
  1068. # to mess with the GymMembers for the gyms we're updating while we're updating the bridge table.
  1069. with flaskDb.database.transaction():
  1070. # get rid of all the gym members, we're going to insert new records
  1071. if len(gym_details):
  1072. DeleteQuery(GymMember).where(GymMember.gym_id << gym_details.keys()).execute()
  1073.  
  1074. # insert new gym members
  1075. if len(gym_members):
  1076. bulk_upsert(GymMember, gym_members)
  1077.  
  1078. log.info('Upserted %d gyms and %d gym members',
  1079. len(gym_details),
  1080. len(gym_members))
  1081.  
  1082.  
  1083. def db_updater(args, q):
  1084. # The forever loop
  1085. while True:
  1086. try:
  1087.  
  1088. while True:
  1089. try:
  1090. flaskDb.connect_db()
  1091. break
  1092. except Exception as e:
  1093. log.warning('%s... Retrying', e)
  1094.  
  1095. # Loop the queue
  1096. while True:
  1097. model, data = q.get()
  1098. bulk_upsert(model, data)
  1099. q.task_done()
  1100. log.debug('Upserted to %s, %d records (upsert queue remaining: %d)',
  1101. model.__name__,
  1102. len(data),
  1103. q.qsize())
  1104. if q.qsize() > 50:
  1105. log.warning("DB queue is > 50 (@%d); try increasing --db-threads", q.qsize())
  1106.  
  1107. except Exception as e:
  1108. log.exception('Exception in db_updater: %s', e)
  1109.  
  1110.  
  1111. def clean_db_loop(args):
  1112. while True:
  1113. try:
  1114. # Clean out old scanned locations
  1115. query = (ScannedLocation
  1116. .delete()
  1117. .where((ScannedLocation.last_modified <
  1118. (datetime.utcnow() - timedelta(minutes=30)))))
  1119. query.execute()
  1120.  
  1121. query = (MainWorker
  1122. .delete()
  1123. .where((ScannedLocation.last_modified <
  1124. (datetime.utcnow() - timedelta(minutes=30)))))
  1125. query.execute()
  1126.  
  1127. query = (WorkerStatus
  1128. .delete()
  1129. .where((ScannedLocation.last_modified <
  1130. (datetime.utcnow() - timedelta(minutes=30)))))
  1131. query.execute()
  1132.  
  1133. # Remove active modifier from expired lured pokestops
  1134. query = (Pokestop
  1135. .update(lure_expiration=None, active_fort_modifier=None)
  1136. .where(Pokestop.lure_expiration < datetime.utcnow()))
  1137. query.execute()
  1138.  
  1139. # If desired, clear old pokemon spawns
  1140. if args.purge_data > 0:
  1141. query = (Pokemon
  1142. .delete()
  1143. .where((Pokemon.disappear_time <
  1144. (datetime.utcnow() - timedelta(hours=args.purge_data)))))
  1145. query.execute()
  1146.  
  1147. log.info('Regular database cleaning complete')
  1148. time.sleep(60)
  1149. except Exception as e:
  1150. log.exception('Exception in clean_db_loop: %s', e)
  1151.  
  1152.  
  1153. def bulk_upsert(cls, data):
  1154. num_rows = len(data.values())
  1155. i = 0
  1156.  
  1157. if args.db_type == 'mysql':
  1158. step = 120
  1159. else:
  1160. # SQLite has a default max number of parameters of 999,
  1161. # so we need to limit how many rows we insert for it.
  1162. step = 50
  1163.  
  1164. while i < num_rows:
  1165. log.debug('Inserting items %d to %d', i, min(i + step, num_rows))
  1166. try:
  1167. InsertQuery(cls, rows=data.values()[i:min(i + step, num_rows)]).upsert().execute()
  1168. except Exception as e:
  1169. log.warning('%s... Retrying', e)
  1170. continue
  1171.  
  1172. i += step
  1173.  
  1174.  
  1175. def create_tables(db):
  1176. db.connect()
  1177. verify_database_schema(db)
  1178. db.create_tables([Pokemon, Pokestop, Gym, ScannedLocation, GymDetails, GymMember, GymPokemon, Trainer, MainWorker, WorkerStatus], safe=True)
  1179. db.close()
  1180.  
  1181.  
  1182. def drop_tables(db):
  1183. db.connect()
  1184. db.drop_tables([Pokemon, Pokestop, Gym, ScannedLocation, Versions, GymDetails, GymMember, GymPokemon, Trainer, MainWorker, WorkerStatus, Versions], safe=True)
  1185. db.close()
  1186.  
  1187.  
  1188. def verify_database_schema(db):
  1189. if not Versions.table_exists():
  1190. db.create_tables([Versions])
  1191.  
  1192. if ScannedLocation.table_exists():
  1193. # Versions table didn't exist, but there were tables. This must mean the user
  1194. # is coming from a database that existed before we started tracking the schema
  1195. # version. Perform a full upgrade.
  1196. InsertQuery(Versions, {Versions.key: 'schema_version', Versions.val: 0}).execute()
  1197. database_migrate(db, 0)
  1198. else:
  1199. InsertQuery(Versions, {Versions.key: 'schema_version', Versions.val: db_schema_version}).execute()
  1200.  
  1201. else:
  1202. db_ver = Versions.get(Versions.key == 'schema_version').val
  1203.  
  1204. if db_ver < db_schema_version:
  1205. database_migrate(db, db_ver)
  1206.  
  1207. elif db_ver > db_schema_version:
  1208. log.error("Your database version (%i) appears to be newer than the code supports (%i).",
  1209. db_ver, db_schema_version)
  1210. log.error("Please upgrade your code base or drop all tables in your database.")
  1211. sys.exit(1)
  1212.  
  1213.  
  1214. def database_migrate(db, old_ver):
  1215. # Update database schema version
  1216. Versions.update(val=db_schema_version).where(Versions.key == 'schema_version').execute()
  1217.  
  1218. log.info("Detected database version %i, updating to %i", old_ver, db_schema_version)
  1219.  
  1220. # Perform migrations here
  1221. migrator = None
  1222. if args.db_type == 'mysql':
  1223. migrator = MySQLMigrator(db)
  1224. else:
  1225. migrator = SqliteMigrator(db)
  1226.  
  1227. # No longer necessary, we're doing this at schema 4 as well
  1228. # if old_ver < 1:
  1229. # db.drop_tables([ScannedLocation])
  1230.  
  1231. if old_ver < 2:
  1232. migrate(migrator.add_column('pokestop', 'encounter_id', CharField(max_length=50, null=True)))
  1233.  
  1234. if old_ver < 3:
  1235. migrate(
  1236. migrator.add_column('pokestop', 'active_fort_modifier', CharField(max_length=50, null=True)),
  1237. migrator.drop_column('pokestop', 'encounter_id'),
  1238. migrator.drop_column('pokestop', 'active_pokemon_id')
  1239. )
  1240.  
  1241. if old_ver < 4:
  1242. db.drop_tables([ScannedLocation])
  1243.  
  1244. if old_ver < 5:
  1245. # Some pokemon were added before the 595 bug was "fixed"
  1246. # Clean those up for a better UX
  1247. query = (Pokemon
  1248. .delete()
  1249. .where(Pokemon.disappear_time >
  1250. (datetime.utcnow() - timedelta(hours=24))))
  1251. query.execute()
  1252.  
  1253. if old_ver < 6:
  1254. migrate(
  1255. migrator.add_column('gym', 'last_scanned', DateTimeField(null=True)),
  1256. )
  1257.  
  1258. if old_ver < 7:
  1259. migrate(
  1260. migrator.drop_column('gymdetails', 'description'),
  1261. migrator.add_column('gymdetails', 'description', TextField(null=True, default=""))
  1262. )
  1263.  
  1264. if old_ver < 8:
  1265. migrate(
  1266. migrator.add_column('pokemon', 'individual_attack', IntegerField(null=True, default=0)),
  1267. migrator.add_column('pokemon', 'individual_defense', IntegerField(null=True, default=0)),
  1268. migrator.add_column('pokemon', 'individual_stamina', IntegerField(null=True, default=0)),
  1269. migrator.add_column('pokemon', 'move_1', IntegerField(null=True, default=0)),
  1270. migrator.add_column('pokemon', 'move_2', IntegerField(null=True, default=0))
  1271. )
  1272.  
  1273. if old_ver < 9:
  1274. migrate(
  1275. migrator.add_column('pokemon', 'last_modified', DateTimeField(null=True, index=True)),
  1276. migrator.add_column('pokestop', 'last_updated', DateTimeField(null=True, index=True))
  1277. )
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement