prompt: You will be provided with a partial code base and an issue statement explaining a problem to resolve.
  <issue>
  Fix multiple file support in linux.file_watch.line + black + fstring
  When multiple file_watch rules are defined, the last defined file reference is used for all files being watched.  This causes trigger-instances to fail rule enforcement.
  
  Adding the reference to the logging shows `test1.log` has the reference ending with `8c505`
  ```
  2021-11-30 18:50:40,434 140243179888112 INFO file_watch_sensor [-] Added file "/var/log/test1.log" with reference linux.7e55ad75-b10c-44db-b53e-95164a18c505
  2021-11-30 18:50:41,459 140243179888112 INFO file_watch_sensor [-] Added file "/var/log/test2.log" with reference linux.590de8c1-c578-4125-9082-2cee03b030a9
  ```
  
  When the file contents are updated a trigger is emitted by the sensor using the reference of `test2.log` ending in `b030a9`
  ```
  root@u1804:~# st2 trigger-instance get 61a6649f164625c2d94dccb8 -y
  id: 61a6649f164625c2d94dccb8
  occurrence_time: '2021-11-30T17:51:27.294000Z'
  payload:
      file_name: test1.log
      file_path: /var/log/test1.log
      line: Tue Nov 30 18:51:27 CET 2021 dhcp
  status: processed
  trigger: linux.590de8c1-c578-4125-9082-2cee03b030a9
  ```
  
  This PR consists of adding a dictionary that is used to track the `path_name` and `reference` pair and looks up the reference for the file that was altered when creating the trigger.
  
  The code is formatted with black and updated to use fstrings since all instances will be using Python 3.6+
  
  </issue>
  <code>
  [start of contrib/linux/sensors/file_watch_sensor.py]
  1 # Copyright 2020 The StackStorm Authors.
  2 # Copyright 2019 Extreme Networks, Inc.
  3 #
  4 # Licensed under the Apache License, Version 2.0 (the "License");
  5 # you may not use this file except in compliance with the License.
  6 # You may obtain a copy of the License at
  7 #
  8 #     http://www.apache.org/licenses/LICENSE-2.0
  9 #
  10 # Unless required by applicable law or agreed to in writing, software
  11 # distributed under the License is distributed on an "AS IS" BASIS,
  12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 # See the License for the specific language governing permissions and
  14 # limitations under the License.
  15 
  16 import os
  17 
  18 import eventlet
  19 
  20 from logshipper.tail import Tail
  21 
  22 from st2reactor.sensor.base import Sensor
  23 
  24 
  25 class FileWatchSensor(Sensor):
  26     def __init__(self, sensor_service, config=None):
  27         super(FileWatchSensor, self).__init__(
  28             sensor_service=sensor_service, config=config
  29         )
  30         self._trigger = None
  31         self._logger = self._sensor_service.get_logger(__name__)
  32         self._tail = None
  33 
  34     def setup(self):
  35         self._tail = Tail(filenames=[])
  36         self._tail.handler = self._handle_line
  37         self._tail.should_run = True
  38 
  39     def run(self):
  40         self._tail.run()
  41 
  42     def cleanup(self):
  43         if self._tail:
  44             self._tail.should_run = False
  45 
  46             try:
  47                 self._tail.notifier.stop()
  48             except Exception:
  49                 self._logger.exception("Unable to stop the tail notifier")
  50 
  51     def add_trigger(self, trigger):
  52         file_path = trigger["parameters"].get("file_path", None)
  53 
  54         if not file_path:
  55             self._logger.error('Received trigger type without "file_path" field.')
  56             return
  57 
  58         self._trigger = trigger.get("ref", None)
  59 
  60         if not self._trigger:
  61             raise Exception("Trigger %s did not contain a ref." % trigger)
  62 
  63         # Wait a bit to avoid initialization race in logshipper library
  64         eventlet.sleep(1.0)
  65 
  66         self._tail.add_file(filename=file_path)
  67         self._logger.info('Added file "%s"' % (file_path))
  68 
  69     def update_trigger(self, trigger):
  70         pass
  71 
  72     def remove_trigger(self, trigger):
  73         file_path = trigger["parameters"].get("file_path", None)
  74 
  75         if not file_path:
  76             self._logger.error('Received trigger type without "file_path" field.')
  77             return
  78 
  79         self._tail.remove_file(filename=file_path)
  80         self._trigger = None
  81 
  82         self._logger.info('Removed file "%s"' % (file_path))
  83 
  84     def _handle_line(self, file_path, line):
  85         trigger = self._trigger
  86         payload = {
  87             "file_path": file_path,
  88             "file_name": os.path.basename(file_path),
  89             "line": line,
  90         }
  91         self._logger.debug(
  92             "Sending payload %s for trigger %s to sensor_service.", payload, trigger
  93         )
  94         self.sensor_service.dispatch(trigger=trigger, payload=payload)
  95
  [end of contrib/linux/sensors/file_watch_sensor.py]
  </code>
  
  I need you to solve this issue by generating a single patch file that I can apply directly to this repository using git apply. Please respond with a single patch file in the following format.
  <patch>
  diff --git a/file.py b/file.py
  --- a/file.py
  +++ b/file.py
  @@ -1,27 +1,35 @@
   def euclidean(a, b):
  -    while b:
  -        a, b = b, a % b
  -    return a
  +    if b == 0:
  +        return a
  +    return euclidean(b, a % b)
   
   
   def bresenham(x0, y0, x1, y1):
       points = []
       dx = abs(x1 - x0)
       dy = abs(y1 - y0)
  -    sx = 1 if x0 < x1 else -1
  -    sy = 1 if y0 < y1 else -1
  -    err = dx - dy
  +    x, y = x0, y0
  +    sx = -1 if x0 > x1 else 1
  +    sy = -1 if y0 > y1 else 1
   
  -    while True:
  -        points.append((x0, y0))
  -        if x0 == x1 and y0 == y1:
  -            break
  -        e2 = 2 * err
  -        if e2 > -dy:
  -            err -= dy
  -            x0 += sx
  -        if e2 < dx:
  -            err += dx
  -            y0 += sy
  +    if dx > dy:
  +        err = dx / 2.0
  +        while x != x1:
  +            points.append((x, y))
  +            err -= dy
  +            if err < 0:
  +                y += sy
  +                err += dx
  +            x += sx
  +    else:
  +        err = dy / 2.0
  +        while y != y1:
  +            points.append((x, y))
  +            err -= dx
  +            if err < 0:
  +                x += sx
  +                err += dy
  +            y += sy
  +
  +    points.append((x, y))
       return points
  
  </patch>
golden_diff: diff --git a/contrib/linux/sensors/file_watch_sensor.py b/contrib/linux/sensors/file_watch_sensor.py
  --- a/contrib/linux/sensors/file_watch_sensor.py
  +++ b/contrib/linux/sensors/file_watch_sensor.py
  @@ -14,7 +14,6 @@
   # limitations under the License.
   
   import os
  -
   import eventlet
   
   from logshipper.tail import Tail
  @@ -27,44 +26,46 @@
           super(FileWatchSensor, self).__init__(
               sensor_service=sensor_service, config=config
           )
  -        self._trigger = None
  -        self._logger = self._sensor_service.get_logger(__name__)
  -        self._tail = None
  +        self.log = self._sensor_service.get_logger(__name__)
  +        self.tail = None
  +        self.file_ref = {}
   
       def setup(self):
  -        self._tail = Tail(filenames=[])
  -        self._tail.handler = self._handle_line
  -        self._tail.should_run = True
  +        self.tail = Tail(filenames=[])
  +        self.tail.handler = self._handle_line
  +        self.tail.should_run = True
   
       def run(self):
  -        self._tail.run()
  +        self.tail.run()
   
       def cleanup(self):
  -        if self._tail:
  -            self._tail.should_run = False
  +        if self.tail:
  +            self.tail.should_run = False
   
               try:
  -                self._tail.notifier.stop()
  +                self.tail.notifier.stop()
               except Exception:
  -                self._logger.exception("Unable to stop the tail notifier")
  +                self.log.exception("Unable to stop the tail notifier")
   
       def add_trigger(self, trigger):
           file_path = trigger["parameters"].get("file_path", None)
   
           if not file_path:
  -            self._logger.error('Received trigger type without "file_path" field.')
  +            self.log.error('Received trigger type without "file_path" field.')
               return
   
  -        self._trigger = trigger.get("ref", None)
  +        trigger = trigger.get("ref", None)
   
  -        if not self._trigger:
  -            raise Exception("Trigger %s did not contain a ref." % trigger)
  +        if not trigger:
  +            raise Exception(f"Trigger {trigger} did not contain a ref.")
   
           # Wait a bit to avoid initialization race in logshipper library
           eventlet.sleep(1.0)
   
  -        self._tail.add_file(filename=file_path)
  -        self._logger.info('Added file "%s"' % (file_path))
  +        self.tail.add_file(filename=file_path)
  +        self.file_ref[file_path] = trigger
  +
  +        self.log.info(f"Added file '{file_path}' ({trigger}) to watch list.")
   
       def update_trigger(self, trigger):
           pass
  @@ -73,22 +74,28 @@
           file_path = trigger["parameters"].get("file_path", None)
   
           if not file_path:
  -            self._logger.error('Received trigger type without "file_path" field.')
  +            self.log.error("Received trigger type without 'file_path' field.")
               return
   
  -        self._tail.remove_file(filename=file_path)
  -        self._trigger = None
  +        self.tail.remove_file(filename=file_path)
  +        self.file_ref.pop(file_path)
   
  -        self._logger.info('Removed file "%s"' % (file_path))
  +        self.log.info(f"Removed file '{file_path}' ({trigger}) from watch list.")
   
       def _handle_line(self, file_path, line):
  -        trigger = self._trigger
  +        if file_path not in self.file_ref:
  +            self.log.error(
  +                f"No reference found for {file_path}, unable to emit trigger!"
  +            )
  +            return
  +
  +        trigger = self.file_ref[file_path]
           payload = {
               "file_path": file_path,
               "file_name": os.path.basename(file_path),
               "line": line,
           }
  -        self._logger.debug(
  -            "Sending payload %s for trigger %s to sensor_service.", payload, trigger
  +        self.log.debug(
  +            f"Sending payload {payload} for trigger {trigger} to sensor_service."
           )
           self.sensor_service.dispatch(trigger=trigger, payload=payload)
  
verification_info: {"golden_diff": "diff --git a/contrib/linux/sensors/file_watch_sensor.py b/contrib/linux/sensors/file_watch_sensor.py\n--- a/contrib/linux/sensors/file_watch_sensor.py\n+++ b/contrib/linux/sensors/file_watch_sensor.py\n@@ -14,7 +14,6 @@\n # limitations under the License.\n \n import os\n-\n import eventlet\n \n from logshipper.tail import Tail\n@@ -27,44 +26,46 @@\n         super(FileWatchSensor, self).__init__(\n             sensor_service=sensor_service, config=config\n         )\n-        self._trigger = None\n-        self._logger = self._sensor_service.get_logger(__name__)\n-        self._tail = None\n+        self.log = self._sensor_service.get_logger(__name__)\n+        self.tail = None\n+        self.file_ref = {}\n \n     def setup(self):\n-        self._tail = Tail(filenames=[])\n-        self._tail.handler = self._handle_line\n-        self._tail.should_run = True\n+        self.tail = Tail(filenames=[])\n+        self.tail.handler = self._handle_line\n+        self.tail.should_run = True\n \n     def run(self):\n-        self._tail.run()\n+        self.tail.run()\n \n     def cleanup(self):\n-        if self._tail:\n-            self._tail.should_run = False\n+        if self.tail:\n+            self.tail.should_run = False\n \n             try:\n-                self._tail.notifier.stop()\n+                self.tail.notifier.stop()\n             except Exception:\n-                self._logger.exception(\"Unable to stop the tail notifier\")\n+                self.log.exception(\"Unable to stop the tail notifier\")\n \n     def add_trigger(self, trigger):\n         file_path = trigger[\"parameters\"].get(\"file_path\", None)\n \n         if not file_path:\n-            self._logger.error('Received trigger type without \"file_path\" field.')\n+            self.log.error('Received trigger type without \"file_path\" field.')\n             return\n \n-        self._trigger = trigger.get(\"ref\", None)\n+        trigger = trigger.get(\"ref\", None)\n \n-        if not self._trigger:\n-            raise Exception(\"Trigger %s did not contain a ref.\" % trigger)\n+        if not trigger:\n+            raise Exception(f\"Trigger {trigger} did not contain a ref.\")\n \n         # Wait a bit to avoid initialization race in logshipper library\n         eventlet.sleep(1.0)\n \n-        self._tail.add_file(filename=file_path)\n-        self._logger.info('Added file \"%s\"' % (file_path))\n+        self.tail.add_file(filename=file_path)\n+        self.file_ref[file_path] = trigger\n+\n+        self.log.info(f\"Added file '{file_path}' ({trigger}) to watch list.\")\n \n     def update_trigger(self, trigger):\n         pass\n@@ -73,22 +74,28 @@\n         file_path = trigger[\"parameters\"].get(\"file_path\", None)\n \n         if not file_path:\n-            self._logger.error('Received trigger type without \"file_path\" field.')\n+            self.log.error(\"Received trigger type without 'file_path' field.\")\n             return\n \n-        self._tail.remove_file(filename=file_path)\n-        self._trigger = None\n+        self.tail.remove_file(filename=file_path)\n+        self.file_ref.pop(file_path)\n \n-        self._logger.info('Removed file \"%s\"' % (file_path))\n+        self.log.info(f\"Removed file '{file_path}' ({trigger}) from watch list.\")\n \n     def _handle_line(self, file_path, line):\n-        trigger = self._trigger\n+        if file_path not in self.file_ref:\n+            self.log.error(\n+                f\"No reference found for {file_path}, unable to emit trigger!\"\n+            )\n+            return\n+\n+        trigger = self.file_ref[file_path]\n         payload = {\n             \"file_path\": file_path,\n             \"file_name\": os.path.basename(file_path),\n             \"line\": line,\n         }\n-        self._logger.debug(\n-            \"Sending payload %s for trigger %s to sensor_service.\", payload, trigger\n+        self.log.debug(\n+            f\"Sending payload {payload} for trigger {trigger} to sensor_service.\"\n         )\n         self.sensor_service.dispatch(trigger=trigger, payload=payload)\n", "issue": "Fix multiple file support in linux.file_watch.line + black + fstring\nWhen multiple file_watch rules are defined, the last defined file reference is used for all files being watched.  This causes trigger-instances to fail rule enforcement.\r\n\r\nAdding the reference to the logging shows `test1.log` has the reference ending with `8c505`\r\n```\r\n2021-11-30 18:50:40,434 140243179888112 INFO file_watch_sensor [-] Added file \"/var/log/test1.log\" with reference linux.7e55ad75-b10c-44db-b53e-95164a18c505\r\n2021-11-30 18:50:41,459 140243179888112 INFO file_watch_sensor [-] Added file \"/var/log/test2.log\" with reference linux.590de8c1-c578-4125-9082-2cee03b030a9\r\n```\r\n\r\nWhen the file contents are updated a trigger is emitted by the sensor using the reference of `test2.log` ending in `b030a9`\r\n```\r\nroot@u1804:~# st2 trigger-instance get 61a6649f164625c2d94dccb8 -y\r\nid: 61a6649f164625c2d94dccb8\r\noccurrence_time: '2021-11-30T17:51:27.294000Z'\r\npayload:\r\n    file_name: test1.log\r\n    file_path: /var/log/test1.log\r\n    line: Tue Nov 30 18:51:27 CET 2021 dhcp\r\nstatus: processed\r\ntrigger: linux.590de8c1-c578-4125-9082-2cee03b030a9\r\n```\r\n\r\nThis PR consists of adding a dictionary that is used to track the `path_name` and `reference` pair and looks up the reference for the file that was altered when creating the trigger.\r\n\r\nThe code is formatted with black and updated to use fstrings since all instances will be using Python 3.6+\n", "before_files": [{"content": "# Copyright 2020 The StackStorm Authors.\n# Copyright 2019 Extreme Networks, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\n\nimport eventlet\n\nfrom logshipper.tail import Tail\n\nfrom st2reactor.sensor.base import Sensor\n\n\nclass FileWatchSensor(Sensor):\n    def __init__(self, sensor_service, config=None):\n        super(FileWatchSensor, self).__init__(\n            sensor_service=sensor_service, config=config\n        )\n        self._trigger = None\n        self._logger = self._sensor_service.get_logger(__name__)\n        self._tail = None\n\n    def setup(self):\n        self._tail = Tail(filenames=[])\n        self._tail.handler = self._handle_line\n        self._tail.should_run = True\n\n    def run(self):\n        self._tail.run()\n\n    def cleanup(self):\n        if self._tail:\n            self._tail.should_run = False\n\n            try:\n                self._tail.notifier.stop()\n            except Exception:\n                self._logger.exception(\"Unable to stop the tail notifier\")\n\n    def add_trigger(self, trigger):\n        file_path = trigger[\"parameters\"].get(\"file_path\", None)\n\n        if not file_path:\n            self._logger.error('Received trigger type without \"file_path\" field.')\n            return\n\n        self._trigger = trigger.get(\"ref\", None)\n\n        if not self._trigger:\n            raise Exception(\"Trigger %s did not contain a ref.\" % trigger)\n\n        # Wait a bit to avoid initialization race in logshipper library\n        eventlet.sleep(1.0)\n\n        self._tail.add_file(filename=file_path)\n        self._logger.info('Added file \"%s\"' % (file_path))\n\n    def update_trigger(self, trigger):\n        pass\n\n    def remove_trigger(self, trigger):\n        file_path = trigger[\"parameters\"].get(\"file_path\", None)\n\n        if not file_path:\n            self._logger.error('Received trigger type without \"file_path\" field.')\n            return\n\n        self._tail.remove_file(filename=file_path)\n        self._trigger = None\n\n        self._logger.info('Removed file \"%s\"' % (file_path))\n\n    def _handle_line(self, file_path, line):\n        trigger = self._trigger\n        payload = {\n            \"file_path\": file_path,\n            \"file_name\": os.path.basename(file_path),\n            \"line\": line,\n        }\n        self._logger.debug(\n            \"Sending payload %s for trigger %s to sensor_service.\", payload, trigger\n        )\n        self.sensor_service.dispatch(trigger=trigger, payload=payload)\n", "path": "contrib/linux/sensors/file_watch_sensor.py"}]}