Repository: pymedusa/Medusa

Issue:
Plex settings overwritten
since the hotfix https://github.com/pymedusa/Medusa/pull/3179 it seems that the server address is being overwritten. 

I've had local DNS issues so server and server.local both should resolve, but my router is having issues with just server. So right now plex is set to server:32400. Changing in the UI to server.local:32400 sticks when in the UI and the config.ini saves the changes. However minutes later I get a warning and it seems to be reverted back to the initial setting of server:32400. I can't see any real logs for where this would happen unfortunately. 192.168.1.2 is the actual IP.

```
2017-11-13 10:35:25 DEBUG    Thread-19 :: [b16e0e5] GET URL: http://server.local:32400/library/sections [Status: 200]
2017-11-13 10:34:06 WARNING  Thread-16 :: [b16e0e5] PLEX: Error while trying to contact Plex Media Server: error HTTPConnectionPool(host='server', port=32400): Max retries exceeded with url: /library/sections (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7fc2e063b3d0>: Failed to establish a new connection: [Errno 111] Connection refused',))
2017-11-13 10:33:09 DEBUG    Thread-19 :: [b16e0e5] GET URL: http://server.local:32400/library/sections [Status: 200]
2017-11-13 10:29:44 DEBUG    Thread-19 :: [b16e0e5] GET URL: http://192.168.1.2:32400/library/sections/1/refresh [Status: 200]
```


Discussion:
I assume by `server:32400` you mean `server_ip:32400`.  Regardless, it looks like this was a pre-existing bug.  A set was used for the hosts which doesn't retain the order, but removes duplicates.  I don't think duplicates are an issue, but order definitely is important.  I'll push a fix to develop if you would like to test it.

Resolving patch:
diff --git a/medusa/notifiers/plex.py b/medusa/notifiers/plex.py
--- a/medusa/notifiers/plex.py
+++ b/medusa/notifiers/plex.py
@@ -9,6 +9,7 @@
 from medusa.logger.adapters.style import BraceAdapter
 from medusa.session.core import MedusaSession
 
+import requests
 from six import iteritems
 
 try:
@@ -22,13 +23,13 @@
 
 class Notifier(object):
     def __init__(self):
-        self.headers = {
+        self.session = MedusaSession()
+        self.session.headers.update({
             'X-Plex-Device-Name': 'Medusa',
             'X-Plex-Product': 'Medusa Notifier',
             'X-Plex-Client-Identifier': common.USER_AGENT,
-            'X-Plex-Version': '2016.02.10'
-        }
-        self.session = MedusaSession()
+            'X-Plex-Version': app.APP_VERSION,
+        })
 
     @staticmethod
     def _notify_pht(message, title='Medusa', host=None, username=None, password=None, force=False):  # pylint: disable=too-many-arguments
@@ -123,45 +124,55 @@ def update_library(self, ep_obj=None, hosts=None,  # pylint: disable=too-many-ar
 
         file_location = '' if not ep_obj else ep_obj.location
         gen_hosts = generate(hosts)
-        hosts = {x.strip() for x in gen_hosts if x.strip()}
-        hosts_all = hosts_match = {}
-        hosts_failed = set()
+        hosts = (x.strip() for x in gen_hosts if x.strip())
+        all_hosts = {}
+        matching_hosts = {}
+        failed_hosts = set()
+        schema = 'https' if app.PLEX_SERVER_HTTPS else 'http'
 
         for cur_host in hosts:
+            url = '{schema}://{host}/library/sections'.format(
+                schema=schema, host=cur_host
+            )
 
-            url = 'http{0}://{1}/library/sections'.format(('', 's')[bool(app.PLEX_SERVER_HTTPS)], cur_host)
             try:
-                # TODO: SESSION: Check if this needs exception handling.
-                xml_response = self.session.get(url, headers=self.headers).text
-                if not xml_response:
-                    log.warning(u'PLEX: Error while trying to contact Plex Media Server: {0}', cur_host)
-                    hosts_failed.add(cur_host)
-                    continue
-
-                media_container = etree.fromstring(xml_response)
-            except IOError as error:
+                response = self.session.get(url)
+            except requests.RequestException as error:
                 log.warning(u'PLEX: Error while trying to contact Plex Media Server: {0}', ex(error))
-                hosts_failed.add(cur_host)
+                failed_hosts.add(cur_host)
                 continue
-            except Exception as error:
-                if 'invalid token' in str(error):
-                    log.warning(u'PLEX: Please set TOKEN in Plex settings: ')
+
+            try:
+                response.raise_for_status()
+            except requests.RequestException as error:
+                if response.status_code == 401:
+                    log.warning(u'PLEX: Unauthorized. Please set TOKEN or USERNAME and PASSWORD in Plex settings')
                 else:
                     log.warning(u'PLEX: Error while trying to contact Plex Media Server: {0}', ex(error))
-                hosts_failed.add(cur_host)
+                failed_hosts.add(cur_host)
                 continue
+            else:
+                xml_response = response.text
+                if not xml_response:
+                    log.warning(u'PLEX: Error while trying to contact Plex Media Server: {0}', cur_host)
+                    failed_hosts.add(cur_host)
+                    continue
+                else:
+                    media_container = etree.fromstring(xml_response)
 
             sections = media_container.findall('.//Directory')
             if not sections:
                 log.debug(u'PLEX: Plex Media Server not running on: {0}', cur_host)
-                hosts_failed.add(cur_host)
+                failed_hosts.add(cur_host)
                 continue
 
             for section in sections:
                 if 'show' == section.attrib['type']:
-
-                    keyed_host = [(str(section.attrib['key']), cur_host)]
-                    hosts_all.update(keyed_host)
+                    key = str(section.attrib['key'])
+                    keyed_host = {
+                        key: cur_host,
+                    }
+                    all_hosts.update(keyed_host)
                     if not file_location:
                         continue
 
@@ -172,59 +183,88 @@ def update_library(self, ep_obj=None, hosts=None,  # pylint: disable=too-many-ar
                         location_path = re.sub(r'^(.{,2})[/\\]', '', location_path)
 
                         if section_path in location_path:
-                            hosts_match.update(keyed_host)
+                            matching_hosts.update(keyed_host)
 
         if force:
-            return (', '.join(set(hosts_failed)), None)[not len(hosts_failed)]
+            return ', '.join(failed_hosts) if failed_hosts else None
 
-        if hosts_match:
-            log.debug(u'PLEX: Updating hosts where TV section paths match the downloaded show: {0}', ', '.join(set(hosts_match)))
+        if matching_hosts:
+            hosts_try = matching_hosts
+            result = u'PLEX: Updating hosts where TV section paths match the downloaded show: {0}'
         else:
-            log.debug(u'PLEX: Updating all hosts with TV sections: {0}', ', '.join(set(hosts_all)))
+            hosts_try = all_hosts
+            result = u'PLEX: Updating all hosts with TV sections: {0}'
+        log.debug(result.format(', '.join(hosts_try)))
 
-        hosts_try = (hosts_match.copy(), hosts_all.copy())[not len(hosts_match)]
         for section_key, cur_host in iteritems(hosts_try):
 
-            url = 'http{0}://{1}/library/sections/{2}/refresh'.format(('', 's')[bool(app.PLEX_SERVER_HTTPS)], cur_host, section_key)
+            url = '{schema}://{host}/library/sections/{key}/refresh'.format(
+                schema=schema, host=cur_host, key=section_key,
+            )
             try:
-                # TODO: Check if this needs exception handling
-                self.session.get(url, headers=self.headers).text
-            except Exception as error:
+                response = self.session.get(url)
+            except requests.RequestException as error:
                 log.warning(u'PLEX: Error updating library section for Plex Media Server: {0}', ex(error))
-                hosts_failed.add(cur_host)
+                failed_hosts.add(cur_host)
+            else:
+                del response  # request succeeded so response is not needed
 
-        return (', '.join(set(hosts_failed)), None)[not len(hosts_failed)]
+        return ', '.join(failed_hosts) if failed_hosts else None
 
     def get_token(self, username=None, password=None, plex_server_token=None):
+        """
+        Get auth token.
+
+        Try to get the auth token from the argument, the config, the session,
+        or the Plex website in that order.
+
+        :param username: plex.tv username
+        :param password: plex.tv password
+        :param plex_server_token: auth token
+
+        :returns: Plex auth token being used or True if authentication is
+            not required, else None
+        """
         username = username or app.PLEX_SERVER_USERNAME
         password = password or app.PLEX_SERVER_PASSWORD
         plex_server_token = plex_server_token or app.PLEX_SERVER_TOKEN
 
         if plex_server_token:
-            self.headers['X-Plex-Token'] = plex_server_token
+            self.session.headers['X-Plex-Token'] = plex_server_token
 
-        if 'X-Plex-Token' in self.headers:
-            return True
+        if 'X-Plex-Token' in self.session.headers:
+            return self.session.headers['X-Plex-Token']
 
         if not (username and password):
             return True
 
         log.debug(u'PLEX: fetching plex.tv credentials for user: {0}', username)
+        error_msg = u'PLEX: Error fetching credentials from plex.tv for user {0}: {1}'
+        try:  # sign in
+            response = self.session.post(
+                'https://plex.tv/users/sign_in.json',
+                data={
+                    'user[login]': username,
+                    'user[password]': password,
+                }
+            )
+            response.raise_for_status()
+        except requests.RequestException as error:
+            log.debug(error_msg, username, error)
+            return
+
+        try:  # get json data
+            data = response.json()
+        except ValueError as error:
+            log.debug(error_msg, username, error)
+            return
+
+        try:  # get token from key
+            plex_server_token = data['user']['authentication_token']
+        except KeyError as error:
+            log.debug(error_msg, username, error)
+            return
+        else:
+            self.session.headers['X-Plex-Token'] = plex_server_token
 
-        params = {
-            'user[login]': username,
-            'user[password]': password
-        }
-
-        try:
-            response = self.session.post('https://plex.tv/users/sign_in.json',
-                                         data=params,
-                                         headers=self.headers).json()
-
-            self.headers['X-Plex-Token'] = response['user']['authentication_token']
-
-        except Exception as error:
-            self.headers.pop('X-Plex-Token', '')
-            log.debug(u'PLEX: Error fetching credentials from from plex.tv for user {0}: {1}', username, error)
-
-        return 'X-Plex-Token' in self.headers
+        return self.session.headers.get('X-Plex-Token')


Test patch: