Repository: RocketMap/RocketMap

Issue:
Send 'gym_details' webhook when gym members change only.
<!--

  ISSUES ARE NOT FOR SUPPORT!!!
  ISSUES ARE NOT FOR SUPPORT!!!
  ISSUES ARE NOT FOR SUPPORT!!!
  ISSUES ARE NOT FOR SUPPORT!!!
  ISSUES ARE NOT FOR SUPPORT!!!
  ISSUES ARE NOT FOR SUPPORT!!!
  ISSUES ARE NOT FOR SUPPORT!!!
  ISSUES ARE NOT FOR SUPPORT!!!

  Join discord (channel: #help) for support at this link: https://discord.gg/rocketmap
  
  Submit feature requests @ http://vote.devkat.org
  
-->

<!--- Provide a general summary of the issue in the Title above -->

## Expected Behavior
<!--- If you're describing a bug, tell us what should happen -->
<!--- If you're suggesting a change/improvement, tell us how it should work -->
Sending gym_details webhook if gym members change.

## Current Behavior
<!--- If describing a bug, tell us what happens instead of the expected behavior -->
<!--- If suggesting a change/improvement, explain the difference from current behavior -->
If gym is modified, webhook is send.

## Possible Solution
<!--- Not obligatory, but suggest a fix/reason for the bug, -->
<!--- or ideas how to implement the addition or change -->
Check if gym member change.

## Steps to Reproduce (for bugs)
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
<!--- reproduce this bug. Include code to reproduce, if relevant -->
1.
2.
3.
4.

## Context
<!--- How has this issue affected you? What are you trying to accomplish? -->
<!--- Providing context helps us come up with a solution that is most useful in the real world -->
Sending gym_details to PokeAlarm for notifications.

## Your Environment
<!--- Include as many relevant details about the environment you experienced the bug in -->
* Version used:
* Environment name and version (e.g. Python 2.7):
* Operating System and version (desktop or mobile):



Discussion:

Resolving patch:
diff --git a/pogom/utils.py b/pogom/utils.py
--- a/pogom/utils.py
+++ b/pogom/utils.py
@@ -385,7 +385,7 @@ def get_args():
                         type=float, default=0.25)
     parser.add_argument('-whlfu', '--wh-lfu-size',
                         help='Webhook LFU cache max size.', type=int,
-                        default=1000)
+                        default=2500)
     parser.add_argument('-whsu', '--webhook-scheduler-updates',
                         help=('Send webhook updates with scheduler status ' +
                               '(use with -wh).'),
diff --git a/pogom/webhook.py b/pogom/webhook.py
--- a/pogom/webhook.py
+++ b/pogom/webhook.py
@@ -4,6 +4,7 @@
 import logging
 import requests
 from datetime import datetime
+from cachetools import LFUCache
 from requests_futures.sessions import FuturesSession
 import threading
 from .utils import get_args
@@ -45,7 +46,7 @@ def send_to_webhook(session, message_type, message):
             log.exception(repr(e))
 
 
-def wh_updater(args, queue, key_cache):
+def wh_updater(args, queue, key_caches):
     wh_threshold_timer = datetime.now()
     wh_over_threshold = False
 
@@ -54,29 +55,44 @@ def wh_updater(args, queue, key_cache):
     # connection, giving a performance increase.
     session = __get_requests_session(args)
 
-    # Extract the proper identifier.
+    # Extract the proper identifier. This list also controls which message
+    # types are getting cached.
     ident_fields = {
         'pokestop': 'pokestop_id',
         'pokemon': 'encounter_id',
-        'gym': 'gym_id'
+        'gym': 'gym_id',
+        'gym_details': 'gym_id'
     }
 
+    # Instantiate WH LFU caches for all cached types. We separate the caches
+    # by ident_field types, because different ident_field (message) types can
+    # use the same name for their ident field.
+    for key in ident_fields:
+        key_caches[key] = LFUCache(maxsize=args.wh_lfu_size)
+
     # The forever loop.
     while True:
         try:
             # Loop the queue.
             whtype, message = queue.get()
 
+            # Get the proper cache if this type has one.
+            key_cache = None
+
+            if whtype in key_caches:
+                key_cache = key_caches[whtype]
+
+            # Get the unique identifier to check our cache, if it has one.
             ident = message.get(ident_fields.get(whtype), None)
 
             # cachetools in Python2.7 isn't thread safe, so we add a lock.
             with wh_lock:
                 # Only send if identifier isn't already in cache.
-                if ident is None:
-                    # We don't know what it is, so let's just log and send
-                    # as-is.
+                if ident is None or key_cache is None:
+                    # We don't know what it is, or it doesn't have a cache,
+                    # so let's just log and send as-is.
                     log.debug(
-                        'Sending webhook item of unknown type: %s.', whtype)
+                        'Sending webhook item of uncached type: %s.', whtype)
                     send_to_webhook(session, whtype, message)
                 elif ident not in key_cache:
                     key_cache[ident] = message
@@ -97,6 +113,7 @@ def wh_updater(args, queue, key_cache):
                         log.debug('Not resending %s to webhook: %s.',
                                   whtype, ident)
 
+            # Helping out the GC.
             del whtype
             del message
             del ident
@@ -173,7 +190,8 @@ def __get_key_fields(whtype):
                     'individual_stamina', 'individual_defense',
                     'individual_attack'],
         'gym': ['team_id', 'guard_pokemon_id',
-                'gym_points', 'enabled', 'latitude', 'longitude']
+                'gym_points', 'enabled', 'latitude', 'longitude'],
+        'gym_details': ['latitude', 'longitude', 'team', 'pokemon']
     }
 
     return key_fields.get(whtype, [])
diff --git a/runserver.py b/runserver.py
--- a/runserver.py
+++ b/runserver.py
@@ -13,7 +13,6 @@
 
 from threading import Thread, Event
 from queue import Queue
-from cachetools import LFUCache
 from flask_cors import CORS
 from flask_cache_bust import init_cache_busting
 
@@ -258,12 +257,12 @@ def main():
         t.daemon = True
         t.start()
 
-    # WH updates queue & WH gym/pokéstop unique key LFU cache.
-    # The LFU cache will stop the server from resending the same data an
-    # infinite number of times.
-    # TODO: Rework webhooks entirely so a LFU cache isn't necessary.
+    # WH updates queue & WH unique key LFU caches.
+    # The LFU caches will stop the server from resending the same data an
+    # infinite number of times. The caches will be instantiated in the
+    # webhook's startup code.
     wh_updates_queue = Queue()
-    wh_key_cache = LFUCache(maxsize=args.wh_lfu_size)
+    wh_key_cache = {}
 
     # Thread to process webhook updates.
     for i in range(args.wh_threads):


Test patch: