Repository: pymedusa/Medusa

Issue:
Single proper snatched multiple times
There seems to be some issue that started the last month or so where medusa will download multiple copies of the same proper from different search provider within seconds of each other.  Once I got two from the same search provider only 7 seconds apart as well.  The worst was last night where it snatched the same episode 10 times. 





Discussion:
Already reported.
Probably a duplicate of the issues #4311 fixed.
This will probably be fixed in the next version.

@medariox 
Also, please use the issue template when you open an issue.
@sharkykh 
I didn't have a look at the "proper search" code at all, so it is possible that this is still an issue.
@Tailslide 
What Medusa version are you using? Any debug logs to share?
Hi, here is the version info.. I'm not sure where to find debug logs.
I disabled proper downloading so I probably won't see this issue again but I thought I should report it in case it was helpful.

Medusa Info: | Branch: master                     Commit: c65ca2607fae74405dcceaaf8c02f3d29d0313ea                     Version: v0.2.5                     Database: 44.11
-- | --
Python Version: | 2.7.14 (default, May 28 2018, 14:47:18)  [GCC 4.7.3 20130102 (prerelease)]
SSL Version: | OpenSSL 1.0.2n  7 Dec 2017
OS: | Linux-3.10.105-x86_64-with-glibc2.2.5
Locale: | en_US.UTF-8



Resolving patch:
diff --git a/medusa/search/proper.py b/medusa/search/proper.py
--- a/medusa/search/proper.py
+++ b/medusa/search/proper.py
@@ -9,13 +9,12 @@
 import operator
 import re
 import threading
-import time
 from builtins import map
 from builtins import object
 from builtins import str
 
 from medusa import app, db, helpers
-from medusa.common import DOWNLOADED, SNATCHED, SNATCHED_BEST, SNATCHED_PROPER, SUBTITLED, cpu_presets
+from medusa.common import DOWNLOADED, SNATCHED, SNATCHED_BEST, SNATCHED_PROPER, SUBTITLED
 from medusa.helper.common import enabled_providers
 from medusa.helper.exceptions import AuthException, ex
 from medusa.logger.adapters.style import BraceAdapter
@@ -23,7 +22,7 @@
 from medusa.search.core import filter_results, pick_result, snatch_episode
 from medusa.show.history import History
 
-from six import itervalues
+from six import itervalues, text_type
 
 log = BraceAdapter(logging.getLogger(__name__))
 log.logger.addHandler(logging.NullHandler())
@@ -228,7 +227,7 @@ def _get_proper_results(self):  # pylint: disable=too-many-locals, too-many-bran
 
             # check if we have the episode as DOWNLOADED
             main_db_con = db.DBConnection()
-            sql_results = main_db_con.select(b"SELECT status, quality, release_name "
+            sql_results = main_db_con.select(b"SELECT quality, release_name "
                                              b"FROM tv_episodes WHERE indexer = ? "
                                              b"AND showid = ? AND season = ? "
                                              b"AND episode = ? AND status = ?",
@@ -256,6 +255,7 @@ def _get_proper_results(self):  # pylint: disable=too-many-locals, too-many-bran
             if release_name:
                 release_name_guess = NameParser()._parse_string(release_name)
                 current_codec = release_name_guess.video_codec
+
                 # Ignore proper if codec differs from downloaded release codec
                 if all([current_codec, best_result.parse_result.video_codec,
                         best_result.parse_result.video_codec != current_codec]):
@@ -263,6 +263,7 @@ def _get_proper_results(self):  # pylint: disable=too-many-locals, too-many-bran
                     if best_result.name not in processed_propers_names:
                         self.processed_propers.append({'name': best_result.name, 'date': best_result.date})
                     continue
+
                 streaming_service = release_name_guess.guess.get(u'streaming_service')
                 # Ignore proper if streaming service differs from downloaded release streaming service
                 if best_result.parse_result.guess.get(u'streaming_service') != streaming_service:
@@ -312,8 +313,8 @@ def _get_proper_results(self):  # pylint: disable=too-many-locals, too-many-bran
             # if the show is in our list and there hasn't been a proper already added for that particular episode
             # then add it to our list of propers
             if best_result.indexerid != -1 and (
-                best_result.indexerid, best_result.actual_season, best_result.actual_episodes[0]
-            ) not in list(map(operator.attrgetter('indexerid', 'actual_season', 'actual_episode'), final_propers)):
+                best_result.indexerid, best_result.actual_season, best_result.actual_episodes
+            ) not in list(map(operator.attrgetter('indexerid', 'actual_season', 'actual_episodes'), final_propers)):
                 log.info('Found a desired proper: {name}', {'name': best_result.name})
                 final_propers.append(best_result)
 
@@ -328,43 +329,57 @@ def _download_propers(self, proper_list):
 
         :param proper_list:
         """
-        for cur_proper in proper_list:
+        for candidate in proper_list:
 
             history_limit = datetime.datetime.today() - datetime.timedelta(days=30)
 
             main_db_con = db.DBConnection()
             history_results = main_db_con.select(
-                b'SELECT resource FROM history '
+                b'SELECT resource, proper_tags FROM history '
                 b'WHERE showid = ? '
                 b'AND season = ? '
-                b'AND episode = ? '
+                b'AND episode IN ({episodes}) '
                 b'AND quality = ? '
                 b'AND date >= ? '
-                b'AND action in (?, ?, ?, ?)',
-                [cur_proper.indexerid, cur_proper.actual_season, cur_proper.actual_episode, cur_proper.quality,
+                b'AND action IN (?, ?, ?, ?)'.format(
+                    episodes=','.join(
+                        text_type(ep) for ep in candidate.actual_episodes
+                    ),
+                ),
+                [candidate.indexerid, candidate.actual_season, candidate.quality,
                  history_limit.strftime(History.date_format),
                  DOWNLOADED, SNATCHED, SNATCHED_PROPER, SNATCHED_BEST])
 
-            # make sure that none of the existing history downloads are the same proper we're trying to download
-            # if the result exists in history already we need to skip it
-            clean_proper_name = self._canonical_name(cur_proper.name, clear_extension=True)
-            if any(clean_proper_name == self._canonical_name(cur_result[b'resource'], clear_extension=True)
-                   for cur_result in history_results):
-                log.debug('This proper {result!r} is already in history, skipping it', {'result': cur_proper.name})
-                continue
-            else:
-                # make sure that none of the existing history downloads are the same proper we're trying to download
-                clean_proper_name = self._canonical_name(cur_proper.name)
-                if any(clean_proper_name == self._canonical_name(cur_result[b'resource'])
-                       for cur_result in history_results):
-                    log.debug('This proper {result!r} is already in history, skipping it', {'result': cur_proper.name})
-                    continue
-
-                cur_proper.create_episode_object()
+            proper_tags_len = len(candidate.proper_tags)
+            proper_name = self._canonical_name(candidate.name, clear_extension=True)
+            proper_name_ext = self._canonical_name(candidate.name)
+
+            for result in history_results:
+
+                proper_tags = result[b'proper_tags']
+                if proper_tags and len(proper_tags.split('|')) >= proper_tags_len:
+                    log.debug(
+                        'Current release has the same or more proper tags,'
+                        ' skipping new proper {result!r}',
+                        {'result': candidate.name},
+                    )
+                    break
+
+                # make sure that none of the existing history downloads are the same proper we're
+                # trying to downloadif the result exists in history already we need to skip it
+                if proper_name == self._canonical_name(
+                    result[b'resource'], clear_extension=True
+                ) or proper_name_ext == self._canonical_name(result[b'resource']):
+                    log.debug(
+                        'This proper {result!r} is already in history, skipping it',
+                        {'result': candidate.name},
+                    )
+                    break
 
+            else:
+                candidate.create_episode_object()
                 # snatch it
-                snatch_episode(cur_proper)
-                time.sleep(cpu_presets[app.CPU_PRESET])
+                snatch_episode(candidate)
 
     @staticmethod
     def _canonical_name(name, clear_extension=False):


Test patch: