diff --git a/gui/slick/images/providers/hd4free.png b/gui/slick/images/providers/hd4free.png
new file mode 100644
index 0000000000000000000000000000000000000000..6cf5435727f8fd916e5e68a54a53b6bd650f9954
Binary files /dev/null and b/gui/slick/images/providers/hd4free.png differ
diff --git a/gui/slick/js/parsers.js b/gui/slick/js/parsers.js
index b7be826ea345a3ba6d6aa7340bdc2e3773a68d2b..1635746cbe2ddfa31128466ef1871bb87ff55c31 100644
--- a/gui/slick/js/parsers.js
+++ b/gui/slick/js/parsers.js
@@ -18,7 +18,21 @@ $.tablesorter.addParser({
         return false;
     },
     format: function(s) {
-        return s.replace('hd1080p', 5).replace('hd720p', 4).replace('hd', 3).replace('sd', 2).replace('any', 1).replace('best', 0).replace('custom', 7);
+        var replacements = {
+            'custom': 11,
+            'bluray': 10, // Custom: Only bluray
+            'hd1080p': 9,
+            '1080p': 8, // Custom: Only 1080p
+            'hdtv': 7, // Custom: 1080p and 720p (only HDTV)
+            'web-dl': 6, // Custom: 1080p and 720p (only WEB-DL)
+            'hd720p': 5,
+            '720p': 4, // Custom: Only 720p
+            'hd': 3,
+            'sd': 2,
+            'any': 1,
+            'best': 0
+        };
+        return replacements[s.toLowerCase()];
     },
     type: 'numeric'
 });
diff --git a/gui/slick/js/restart.js b/gui/slick/js/restart.js
index 9c7ec6f0703d5fee800dec7855e7dd0408cb463a..1dc8af1412ed805a29100e2a3f4eba6539b828ab 100644
--- a/gui/slick/js/restart.js
+++ b/gui/slick/js/restart.js
@@ -5,7 +5,7 @@ $(document).ready(function() {
 
     var isAliveUrl = srRoot + '/home/is_alive/';
 
-    var checkIsAlive = setInterval(isAlive, 1000);
+    var checkIsAlive = setInterval(isAlive, 100);
 
     function isAlive() {  // jshint ignore:line
         // Setup error detection
diff --git a/gui/slick/views/config_subtitles.mako b/gui/slick/views/config_subtitles.mako
index 720b10cc23e5274acc53a981bf0a75dfaa3651bc..d1cee5a2085150a8e5cef6f898ccb450f176f00d 100644
--- a/gui/slick/views/config_subtitles.mako
+++ b/gui/slick/views/config_subtitles.mako
@@ -83,6 +83,16 @@ $('#subtitles_dir').fileBrowser({ title: 'Select Subtitles Download Directory' }
                                         <span class="component-desc">time in hours between scans (default: 1)</span>
                                     </label>
                                 </div>
+                                <div class="field-pair">
+                                    <label for="subtitles_perfect_match" class="clearfix">
+                                        <span class="component-title">Perfect matches</span>
+                                        <span class="component-desc">
+                                            <input type="checkbox" class="enabler" ${('', ' checked="checked"')[bool(sickbeard.SUBTITLES_PERFECT_MATCH)]} id="subtitles_perfect_match" name="subtitles_perfect_match">
+                                            <p>Only download subtitles that match: release group, video codec, audio codec and resolution</p>
+                                            <p>If disabled you may get out of sync subtitles</p>
+                                        </span>
+                                    </label>
+                                </div>
                                 <div class="field-pair">
                                     <label class="clearfix" for="subtitles_history">
                                         <span class="component-title">Subtitles History</span>
diff --git a/gui/slick/views/displayShow.mako b/gui/slick/views/displayShow.mako
index 9b7515d7ff7681da6e9bb316a590fcb9c70c674d..c071de73206444bf3871d404defdf3a90232274a 100644
--- a/gui/slick/views/displayShow.mako
+++ b/gui/slick/views/displayShow.mako
@@ -126,9 +126,12 @@
                 <img src="${srRoot}/images/blank.png" class="country-flag flag-${country}" width="16" height="11" style="margin-left: 3px; vertical-align:middle;" />
         % endfor
     % endif
-    % if 'year' in show.imdb_info:
-                <span>(${show.imdb_info['year']}) - ${show.imdb_info['runtimes']} minutes - </span>
+    <span>
+    % if 'year' in show.imdb_info and show.imdb_info['year']:
+                (${show.imdb_info['year']}) -
     % endif
+                ${show.imdb_info['runtimes']} minutes</span>
+
                 <a href="${anon_url('http://www.imdb.com/title/', _show.imdbid)}" rel="noreferrer" onclick="window.open(this.href, '_blank'); return false;" title="http://www.imdb.com/title/${show.imdbid}"><img alt="[imdb]" height="16" width="16" src="${srRoot}/images/imdb.png" style="margin-top: -1px; vertical-align:middle;"/></a>
 % endif
                 <a href="${anon_url(sickbeard.indexerApi(_show.indexer).config['show_url'], _show.indexerid)}" onclick="window.open(this.href, '_blank'); return false;" title="${sickbeard.indexerApi(show.indexer).config["show_url"] + str(show.indexerid)}"><img alt="${sickbeard.indexerApi(show.indexer).name}" height="16" width="16" src="${srRoot}/images/${sickbeard.indexerApi(show.indexer).config["icon"]}" style="margin-top: -1px; vertical-align:middle;"/></a>
@@ -139,14 +142,13 @@
 
             <div id="tags">
                 <ul class="tags">
-                    % if not show.imdbid and show.genre:
+                    % if ('genres' not in show.imdb_info or not show.imdb_info['genres']) and show.genre:
                         % for genre in show.genre[1:-1].split('|'):
                             <a href="${anon_url('http://trakt.tv/shows/popular/?genres=', genre.lower())}" target="_blank" title="View other popular ${genre} shows on trakt.tv."><li>${genre}</li></a>
                         % endfor
-                    % endif
-                    % if 'year' in show.imdb_info:
+                    % elif 'genres' in show.imdb_info and show.imdb_info['genres']:
                         % for imdbgenre in show.imdb_info['genres'].replace('Sci-Fi','Science-Fiction').split('|'):
-                            <a href="${anon_url('http://trakt.tv/shows/popular/?genres=', imdbgenre.lower())}" target="_blank" title="View other popular ${imdbgenre} shows on trakt.tv."><li>${imdbgenre}</li></a>
+                            <a href="${anon_url('http://www.imdb.com/search/title?count=100&title_type=tv_series&genres=', imdbgenre.lower())}" target="_blank" title="View other popular ${imdbgenre} shows on IMDB."><li>${imdbgenre}</li></a>
                         % endfor
                     % endif
                 </ul>
@@ -519,10 +521,8 @@
                         <a class="epSearch" id="${str(show.indexerid)}x${str(epResult["season"])}x${str(epResult["episode"])}" name="${str(show.indexerid)}x${str(epResult["season"])}x${str(epResult["episode"])}" href="searchEpisode?show=${show.indexerid}&amp;season=${epResult["season"]}&amp;episode=${epResult["episode"]}"><img src="${srRoot}/images/search16.png" width="16" height="16" alt="search" title="Manual Search" /></a>
                     % endif
                 % endif
-                % if sickbeard.USE_SUBTITLES and show.subtitles and epResult["location"]:
-                    % if (sickbeard.SUBTITLES_MULTI and subtitles.needs_subtitles(epResult["subtitles"])) or (not sickbeard.SUBTITLES_MULTI and len(subtitles.wanted_languages()) > 0 and "und" not in epResult["subtitles"] and list(subtitles.wanted_languages())[0] not in epResult["subtitles"]):
-                        <a class="epSubtitlesSearch" href="searchEpisodeSubtitles?show=${show.indexerid}&amp;season=${epResult["season"]}&amp;episode=${epResult["episode"]}"><img src="${srRoot}/images/closed_captioning.png" height="16" alt="search subtitles" title="Search Subtitles" /></a>
-                    % endif
+                % if sickbeard.USE_SUBTITLES and show.subtitles and epResult["location"] and subtitles.needs_subtitles(epResult['subtitles']):
+                    <a class="epSubtitlesSearch" href="searchEpisodeSubtitles?show=${show.indexerid}&amp;season=${epResult["season"]}&amp;episode=${epResult["episode"]}"><img src="${srRoot}/images/closed_captioning.png" height="16" alt="search subtitles" title="Search Subtitles" /></a>
                 % endif
             </td>
         </tr>
diff --git a/gui/slick/views/inc_defs.mako b/gui/slick/views/inc_defs.mako
index f8ff29646e906f4cc13b9d163d4f77cec5711fe6..94200437394fe008ff15336a0870be664dfa9128 100644
--- a/gui/slick/views/inc_defs.mako
+++ b/gui/slick/views/inc_defs.mako
@@ -5,16 +5,16 @@
 <%def name="renderQualityPill(quality, showTitle=False, overrideClass=None)"><%
     # Build a string of quality names to use as title attribute
     if showTitle:
-        iQuality, pQuality = Quality.splitQuality(quality)
+        iquality, pquality = Quality.splitQuality(quality)
         title = 'Initial Quality:\n'
-        if iQuality:
-            for curQual in iQuality:
+        if iquality:
+            for curQual in iquality:
                 title += "  " + Quality.qualityStrings[curQual] + "\n"
         else:
             title += "  None\n"
         title += "\nPreferred Quality:\n"
-        if pQuality:
-            for curQual in pQuality:
+        if pquality:
+            for curQual in pquality:
                 title += "  " + Quality.qualityStrings[curQual] + "\n"
         else:
             title += "  None\n"
@@ -22,12 +22,17 @@
     else:
         title = ""
 
-    iQuality = quality & 0xFFFF
-    pQuality = quality >> 16
+    sum_iquality = quality & 0xFFFF
+    sum_pquality = quality >> 16
+    set_hdtv = set([Quality.HDTV, Quality.RAWHDTV, Quality.FULLHDTV])
+    set_webdl = set([Quality.HDWEBDL, Quality.FULLHDWEBDL])
+    set_bluray = set([Quality.HDBLURAY, Quality.FULLHDBLURAY])
+    set_1080p = set([Quality.FULLHDTV, Quality.FULLHDWEBDL, Quality.FULLHDBLURAY])
+    set_720p = set([Quality.HDTV, Quality.RAWHDTV, Quality.HDWEBDL, Quality.HDBLURAY])
 
     # If initial and preferred qualities are the same, show pill as initial quality
-    if iQuality == pQuality:
-        quality = iQuality
+    if sum_iquality == sum_pquality:
+        quality = sum_iquality
 
     if quality in qualityPresets:
         cssClass = qualityPresetStrings[quality]
@@ -38,6 +43,26 @@
     elif quality in Quality.qualityStrings:
         cssClass = Quality.cssClassStrings[quality]
         qualityString = Quality.qualityStrings[quality]
+    # Check if all sources are HDTV
+    elif set(iquality).issubset(set_hdtv)and set(pquality).issubset(set_hdtv):
+        cssClass = Quality.cssClassStrings[Quality.ANYHDTV]
+        qualityString = 'HDTV'
+    # Check if all sources are WEB-DL
+    elif set(iquality).issubset(set_webdl)and set(pquality).issubset(set_webdl):
+        cssClass = Quality.cssClassStrings[Quality.ANYWEBDL]
+        qualityString = 'WEB-DL'
+    # Check if all sources are BLURAY
+    elif set(iquality).issubset(set_bluray)and set(pquality).issubset(set_bluray):
+        cssClass = Quality.cssClassStrings[Quality.ANYBLURAY]
+        qualityString = 'BLURAY'
+    # Check if all resolutions are 1080p
+    elif set(iquality).issubset(set_1080p)and set(pquality).issubset(set_1080p):
+        cssClass = Quality.cssClassStrings[Quality.FULLHDBLURAY]
+        qualityString = '1080p'
+    # Check if all resolutions are 720p
+    elif set(iquality).issubset(set_720p)and set(pquality).issubset(set_720p):
+        cssClass = Quality.cssClassStrings[Quality.HDBLURAY]
+        qualityString = '720p'
     else:
         cssClass = "Custom"
         qualityString = "Custom"
diff --git a/sickbeard/__init__.py b/sickbeard/__init__.py
index 8cd96b162a23a70ae3ea6a43ee265096bdce6767..75508c6998827408151750b16811abc869ae1849 100644
--- a/sickbeard/__init__.py
+++ b/sickbeard/__init__.py
@@ -519,6 +519,7 @@ SUBTITLES_DIR = ''
 SUBTITLES_SERVICES_LIST = []
 SUBTITLES_SERVICES_ENABLED = []
 SUBTITLES_HISTORY = False
+SUBTITLES_PERFECT_MATCH = False
 EMBEDDED_SUBTITLES_ALL = False
 SUBTITLES_HEARING_IMPAIRED = False
 SUBTITLES_FINDER_FREQUENCY = 1
@@ -612,7 +613,7 @@ def initialize(consoleLogging=True):
             GUI_NAME, HOME_LAYOUT, HISTORY_LAYOUT, DISPLAY_SHOW_SPECIALS, COMING_EPS_LAYOUT, COMING_EPS_SORT, COMING_EPS_DISPLAY_PAUSED, COMING_EPS_MISSED_RANGE, FUZZY_DATING, TRIM_ZERO, DATE_PRESET, TIME_PRESET, TIME_PRESET_W_SECONDS, THEME_NAME, \
             POSTER_SORTBY, POSTER_SORTDIR, HISTORY_LIMIT, CREATE_MISSING_SHOW_DIRS, ADD_SHOWS_WO_DIR, \
             METADATA_WDTV, METADATA_TIVO, METADATA_MEDE8ER, IGNORE_WORDS, TRACKERS_LIST, IGNORED_SUBS_LIST, REQUIRE_WORDS, CALENDAR_UNPROTECTED, CALENDAR_ICONS, NO_RESTART, \
-            USE_SUBTITLES, SUBTITLES_LANGUAGES, SUBTITLES_DIR, SUBTITLES_SERVICES_LIST, SUBTITLES_SERVICES_ENABLED, SUBTITLES_HISTORY, SUBTITLES_FINDER_FREQUENCY, SUBTITLES_MULTI, SUBTITLES_DOWNLOAD_IN_PP, EMBEDDED_SUBTITLES_ALL, SUBTITLES_EXTRA_SCRIPTS, subtitlesFinderScheduler, \
+            USE_SUBTITLES, SUBTITLES_LANGUAGES, SUBTITLES_DIR, SUBTITLES_SERVICES_LIST, SUBTITLES_SERVICES_ENABLED, SUBTITLES_HISTORY, SUBTITLES_FINDER_FREQUENCY, SUBTITLES_MULTI, SUBTITLES_DOWNLOAD_IN_PP, EMBEDDED_SUBTITLES_ALL, SUBTITLES_EXTRA_SCRIPTS, SUBTITLES_PERFECT_MATCH, subtitlesFinderScheduler, \
             SUBTITLES_HEARING_IMPAIRED, ADDIC7ED_USER, ADDIC7ED_PASS, LEGENDASTV_USER, LEGENDASTV_PASS, OPENSUBTITLES_USER, OPENSUBTITLES_PASS, \
             USE_FAILED_DOWNLOADS, DELETE_FAILED, ANON_REDIRECT, LOCALHOST_IP, DEBUG, DEFAULT_PAGE, PROXY_SETTING, PROXY_INDEXERS, \
             AUTOPOSTPROCESSER_FREQUENCY, SHOWUPDATE_HOUR, \
@@ -1148,6 +1149,7 @@ def initialize(consoleLogging=True):
                                       if x]
         SUBTITLES_DEFAULT = bool(check_setting_int(CFG, 'Subtitles', 'subtitles_default', 0))
         SUBTITLES_HISTORY = bool(check_setting_int(CFG, 'Subtitles', 'subtitles_history', 0))
+        SUBTITLES_PERFECT_MATCH = bool(check_setting_int(CFG, 'Subtitles', 'subtitles_perfect_match', 1))
         EMBEDDED_SUBTITLES_ALL = bool(check_setting_int(CFG, 'Subtitles', 'embedded_subtitles_all', 0))
         SUBTITLES_HEARING_IMPAIRED = bool(check_setting_int(CFG, 'Subtitles', 'subtitles_hearing_impaired', 0))
         SUBTITLES_FINDER_FREQUENCY = check_setting_int(CFG, 'Subtitles', 'subtitles_finder_frequency', 1)
@@ -1534,82 +1536,30 @@ def halt():
 
             logger.log(u"Aborting all threads")
 
-            events.stop.set()
-            logger.log(u"Waiting for the EVENTS thread to exit")
-            try:
-                events.join(10)
-            except Exception:
-                pass
-
-            dailySearchScheduler.stop.set()
-            logger.log(u"Waiting for the DAILYSEARCH thread to exit")
-            try:
-                dailySearchScheduler.join(10)
-            except Exception:
-                pass
-
-            backlogSearchScheduler.stop.set()
-            logger.log(u"Waiting for the BACKLOG thread to exit")
-            try:
-                backlogSearchScheduler.join(10)
-            except Exception:
-                pass
-
-            showUpdateScheduler.stop.set()
-            logger.log(u"Waiting for the SHOWUPDATER thread to exit")
-            try:
-                showUpdateScheduler.join(10)
-            except Exception:
-                pass
-
-            versionCheckScheduler.stop.set()
-            logger.log(u"Waiting for the VERSIONCHECKER thread to exit")
-            try:
-                versionCheckScheduler.join(10)
-            except Exception:
-                pass
-
-            showQueueScheduler.stop.set()
-            logger.log(u"Waiting for the SHOWQUEUE thread to exit")
-            try:
-                showQueueScheduler.join(10)
-            except Exception:
-                pass
-
-            searchQueueScheduler.stop.set()
-            logger.log(u"Waiting for the SEARCHQUEUE thread to exit")
-            try:
-                searchQueueScheduler.join(10)
-            except Exception:
-                pass
-
-            autoPostProcesserScheduler.stop.set()
-            logger.log(u"Waiting for the POSTPROCESSER thread to exit")
-            try:
-                autoPostProcesserScheduler.join(10)
-            except Exception:
-                pass
-
-            traktCheckerScheduler.stop.set()
-            logger.log(u"Waiting for the TRAKTCHECKER thread to exit")
-            try:
-                traktCheckerScheduler.join(10)
-            except Exception:
-                pass
-
-            properFinderScheduler.stop.set()
-            logger.log(u"Waiting for the PROPERFINDER thread to exit")
-            try:
-                properFinderScheduler.join(10)
-            except Exception:
-                pass
-
-            subtitlesFinderScheduler.stop.set()
-            logger.log(u"Waiting for the SUBTITLESFINDER thread to exit")
-            try:
-                subtitlesFinderScheduler.join(10)
-            except Exception:
-                pass
+            threads = [
+                dailySearchScheduler,
+                backlogSearchScheduler,
+                showUpdateScheduler,
+                versionCheckScheduler,
+                showQueueScheduler,
+                searchQueueScheduler,
+                autoPostProcesserScheduler,
+                traktCheckerScheduler,
+                properFinderScheduler,
+                subtitlesFinderScheduler,
+                events
+            ]
+
+            # set them all to stop at the same time
+            for t in threads:
+                t.stop.set()
+
+            for t in threads:
+                logger.log(u"Waiting for the %s thread to exit" % t.name)
+                try:
+                    t.join(10)
+                except Exception:
+                    pass
 
             if ADBA_CONNECTION:
                 ADBA_CONNECTION.logout()
@@ -1624,6 +1574,7 @@ def halt():
 
 
 def sig_handler(signum=None, frame=None):
+    _ = frame
     if not isinstance(signum, type(None)):
         logger.log(u"Signal %i caught, saving and exiting..." % int(signum))
         Shutdown.stop(PID)
@@ -1640,16 +1591,6 @@ def saveAll():
     save_config()
 
 
-def restart(soft=True):
-    if soft:
-        halt()
-        saveAll()
-        logger.log(u"Re-initializing all data")
-        initialize()
-    else:
-        events.put(events.SystemEvent.RESTART)
-
-
 def save_config():
     new_config = ConfigObj()
     new_config.filename = CONFIG_FILE
@@ -2148,6 +2089,7 @@ def save_config():
     new_config['Subtitles']['subtitles_dir'] = SUBTITLES_DIR
     new_config['Subtitles']['subtitles_default'] = int(SUBTITLES_DEFAULT)
     new_config['Subtitles']['subtitles_history'] = int(SUBTITLES_HISTORY)
+    new_config['Subtitles']['subtitles_perfect_match'] = int(SUBTITLES_PERFECT_MATCH)
     new_config['Subtitles']['embedded_subtitles_all'] = int(EMBEDDED_SUBTITLES_ALL)
     new_config['Subtitles']['subtitles_hearing_impaired'] = int(SUBTITLES_HEARING_IMPAIRED)
     new_config['Subtitles']['subtitles_finder_frequency'] = int(SUBTITLES_FINDER_FREQUENCY)
diff --git a/sickbeard/helpers.py b/sickbeard/helpers.py
index e1e191302e6455060090e11b006be55d29df5fa2..d5dd70ae0c6b10901f3046cf721de0636c20c654 100644
--- a/sickbeard/helpers.py
+++ b/sickbeard/helpers.py
@@ -1285,7 +1285,7 @@ def mapIndexersToShow(showObj):
         if len(nlist) >= 4:
             logger.log(u"Found indexer mapping in cache for show: " + showObj.name, logger.DEBUG)
             mapped[int(curResult['mindexer'])] = int(curResult['mindexer_id'])
-            return mapped
+            break
     else:
         sql_l = []
         for indexer in sickbeard.indexerApi().indexers:
diff --git a/sickbeard/providers/__init__.py b/sickbeard/providers/__init__.py
index 1b447b6d896116b49e3c7102071382662b077e71..3923308c2fcc82f84895f726ec921227d28d906d 100644
--- a/sickbeard/providers/__init__.py
+++ b/sickbeard/providers/__init__.py
@@ -23,10 +23,10 @@ from random import shuffle
 
 import sickbeard
 from sickbeard import logger
-from sickbeard.providers import btn, newznab, womble, thepiratebay, torrentleech, kat, iptorrents, torrentz, \
+from sickbeard.providers import btn, newznab, rsstorrent, womble, thepiratebay, torrentleech, kat, iptorrents, torrentz, \
     omgwtfnzbs, scc, hdtorrents, torrentday, hdbits, hounddawgs, speedcd, nyaatorrents, animenzb, bluetigers, cpasbien, fnt, xthor, torrentbytes, \
     freshontv, titansoftv, morethantv, bitsoup, t411, tokyotoshokan, shazbat, rarbg, alpharatio, tntvillage, binsearch, torrentproject, extratorrent, \
-    scenetime, btdigg, strike, transmitthenet, tvchaosuk, bitcannon, pretome, gftracker, hdspace, newpct, elitetorrent, bitsnoop, danishbits
+    scenetime, btdigg, strike, transmitthenet, tvchaosuk, bitcannon, pretome, gftracker, hdspace, newpct, elitetorrent, bitsnoop, danishbits, hd4free
 
 __all__ = [
     'womble', 'btn', 'thepiratebay', 'kat', 'torrentleech', 'scc', 'hdtorrents',
@@ -36,7 +36,7 @@ __all__ = [
     'shazbat', 'rarbg', 'tntvillage', 'binsearch', 'bluetigers', 'cpasbien',
     'fnt', 'xthor', 'scenetime', 'btdigg', 'strike', 'transmitthenet', 'tvchaosuk',
     'torrentproject', 'extratorrent', 'bitcannon', 'torrentz', 'pretome', 'gftracker',
-    'hdspace', 'newpct', 'elitetorrent', 'bitsnoop', 'danishbits'
+    'hdspace', 'newpct', 'elitetorrent', 'bitsnoop', 'danishbits', 'hd4free'
 ]
 
 
@@ -73,7 +73,7 @@ def makeProviderList():
 
 def getNewznabProviderList(data):
     defaultList = [makeNewznabProvider(x) for x in getDefaultNewznabProviders().split('!!!')]
-    providerList = filter(lambda x: x, [makeNewznabProvider(x) for x in data.split('!!!')])
+    providerList = [x for x in [makeNewznabProvider(x) for x in data.split('!!!')] if x]
 
     seen_values = set()
     providerListDeduped = []
@@ -103,7 +103,7 @@ def getNewznabProviderList(data):
             providerDict[curDefault.name].enable_daily = curDefault.enable_daily
             providerDict[curDefault.name].enable_backlog = curDefault.enable_backlog
 
-    return filter(lambda x: x, providerList)
+    return [x for x in providerList if x]
 
 
 def makeNewznabProvider(configString):
@@ -129,7 +129,7 @@ def makeNewznabProvider(configString):
         logger.log(u"Skipping Newznab provider string: '" + configString + "', incorrect format", logger.ERROR)
         return None
 
-    newznab = sys.modules['sickbeard.providers.newznab']
+    # newznab = sys.modules['sickbeard.providers.newznab']
 
     newProvider = newznab.NewznabProvider(name, url, key=key, catIDs=catIDs, search_mode=search_mode,
                                           search_fallback=search_fallback, enable_daily=enable_daily,
@@ -140,7 +140,7 @@ def makeNewznabProvider(configString):
 
 
 def getTorrentRssProviderList(data):
-    providerList = filter(lambda x: x, [makeTorrentRssProvider(x) for x in data.split('!!!')])
+    providerList = [x for x in [makeTorrentRssProvider(x) for x in data.split('!!!')] if x]
 
     seen_values = set()
     providerListDeduped = []
@@ -150,7 +150,7 @@ def getTorrentRssProviderList(data):
             providerListDeduped.append(d)
             seen_values.add(value)
 
-    return filter(lambda x: x, providerList)
+    return [x for x in providerList if x]
 
 
 def makeTorrentRssProvider(configString):
@@ -179,12 +179,12 @@ def makeTorrentRssProvider(configString):
                    logger.ERROR)
         return None
 
-    try:
-        torrentRss = sys.modules['sickbeard.providers.rsstorrent']
-    except Exception:
-        return
+    # try:
+    #     torrentRss = sys.modules['sickbeard.providers.rsstorrent']
+    # except Exception:
+    #     return
 
-    newProvider = torrentRss.TorrentRssProvider(name, url, cookies, titleTAG, search_mode, search_fallback, enable_daily,
+    newProvider = rsstorrent.TorrentRssProvider(name, url, cookies, titleTAG, search_mode, search_fallback, enable_daily,
                                                 enable_backlog)
     newProvider.enabled = enabled == '1'
 
diff --git a/sickbeard/providers/animenzb.py b/sickbeard/providers/animenzb.py
index 57d23009692293c9dd8d8eb504dad521f648180c..93460b5185da94fddbf51cbd56c4a49752159db9 100644
--- a/sickbeard/providers/animenzb.py
+++ b/sickbeard/providers/animenzb.py
@@ -42,7 +42,7 @@ class animenzb(NZBProvider):
         self.supports_absolute_numbering = True
         self.anime_only = True
 
-        self.urls = {'base_url': 'http://animenzb.com//'}
+        self.urls = {'base_url': 'http://animenzb.com/'}
         self.url = self.urls['base_url']
 
         self.cache = animenzbCache(self)
diff --git a/sickbeard/providers/bitsoup.py b/sickbeard/providers/bitsoup.py
index f8585c739dbd3d1904525f60756e23fe771bc0ab..5060f1504121bd742688825bb808a0a3112b38ed 100644
--- a/sickbeard/providers/bitsoup.py
+++ b/sickbeard/providers/bitsoup.py
@@ -115,8 +115,8 @@ class BitSoupProvider(TorrentProvider):
 
                             try:
                                 title = link.getText()
-                                seeders = int(cells[10].getText())
-                                leechers = int(cells[11].getText())
+                                seeders = int(cells[10].getText().replace(',', ''))
+                                leechers = int(cells[11].getText().replace(',', ''))
                                 # FIXME
                                 size = -1
                             except (AttributeError, TypeError):
@@ -131,6 +131,9 @@ class BitSoupProvider(TorrentProvider):
                                     logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG)
                                 continue
 
+                            if seeders >= 32768 or leechers >= 32768:
+                                continue
+
                             item = title, download_url, size, seeders, leechers
                             if mode != 'RSS':
                                 logger.log(u"Found result: %s " % title, logger.DEBUG)
diff --git a/sickbeard/providers/danishbits.py b/sickbeard/providers/danishbits.py
index 51d466014a22fe575a7673f6224382e282e7e9ca..4f27fc0961fc90eeb14c8ef77cc27abe4d159cb0 100644
--- a/sickbeard/providers/danishbits.py
+++ b/sickbeard/providers/danishbits.py
@@ -58,7 +58,7 @@ class DanishbitsProvider(TorrentProvider):  # pylint: disable=too-many-instance-
 
     @staticmethod
     def loginSuccess(output):
-        if "<title>Login :: Danishbits.org</title>" in output:
+        if not output or "<title>Login :: Danishbits.org</title>" in output:
             return False
         else:
             return True
diff --git a/sickbeard/providers/hd4free.py b/sickbeard/providers/hd4free.py
new file mode 100644
index 0000000000000000000000000000000000000000..f6b6e715e045fbf8a641c24b1dfcdcffc981a1d7
--- /dev/null
+++ b/sickbeard/providers/hd4free.py
@@ -0,0 +1,129 @@
+# coding=utf-8
+# Author: Gonçalo (aka duramato) <matigonkas@outlook.com>
+# URL: https://github.com/SickRage/SickRage
+#
+# This file is part of SickRage.
+#
+# SickRage is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# SickRage is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with SickRage.  If not, see <http://www.gnu.org/licenses/>.
+
+from urllib import urlencode
+from sickbeard import logger
+from sickbeard import tvcache
+from sickrage.providers.TorrentProvider import TorrentProvider
+
+
+class HD4FREEProvider(TorrentProvider):  # pylint: disable=too-many-instance-attributes
+
+    def __init__(self):
+        TorrentProvider.__init__(self, "HD4Free")
+
+        self.public = True
+        self.url = 'https://hd4free.xyz'
+        self.ratio = 0
+        self.cache = HD4FREECache(self)
+        self.minseed, self.minleech = 2 * [None]
+        self.username = None
+        self.api_key = None
+        self.freeleech = None
+
+    def _check_auth(self):
+        if self.username and self.api_key:
+            return True
+
+        logger.log('Your authentication credentials for %s are missing, check your config.' % self.name)
+        return False
+
+    def search(self, search_strings, age=0, ep_obj=None):  # pylint: disable=too-many-locals
+
+        results = []
+        items = {'Season': [], 'Episode': [], 'RSS': []}
+
+        search_params = {
+            'tv': 'true',
+            'username': self.username,
+            'apikey': self.api_key
+        }
+
+        for mode in search_strings.keys():  # Mode = RSS, Season, Episode
+            logger.log(u"Search Mode: %s" % mode, logger.DEBUG)
+            for search_string in search_strings[mode]:
+                if mode != 'RSS':
+                    search_params['search'] = search_string.encode('utf-8')
+
+                search_params['fl'] = 'true' if self.freeleech else 'false'
+
+                if mode != 'RSS':
+                    logger.log(u"Search string: " + search_string.strip(), logger.DEBUG)
+
+                searchURL = self.url + "/searchapi.php?" + urlencode(search_params)
+                logger.log(u"Search URL: %s" % searchURL, logger.DEBUG)
+                jdata = self.get_url(searchURL, json=True)
+                if not jdata:
+                    logger.log(u"No data returned from provider", logger.DEBUG)
+                    continue
+
+                try:
+                    if jdata['0']['total_results'] == 0:
+                        logger.log(u"Provider has no results for this search", logger.DEBUG)
+                        continue
+                except (ValueError, KeyError):
+                    pass
+
+                for i in jdata:
+                    seeders = jdata[i]["seeders"]
+                    leechers = jdata[i]["leechers"]
+                    title = jdata[i]["release_name"]
+                    size = jdata[i]["size"]
+                    download_url = jdata[i]["download_url"]
+
+                    if not all([title, download_url]):
+                        continue
+
+                    # Filter unseeded torrent
+                    if seeders < self.minseed or leechers < self.minleech:
+                        if mode != 'RSS':
+                            logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG)
+                        continue
+
+                    if mode != 'RSS':
+                        logger.log(u"Found result: %s " % title, logger.DEBUG)
+
+                    item = title, download_url, size, seeders, leechers
+                    items[mode].append(item)
+
+            # For each search mode sort all the items by seeders if available
+            items[mode].sort(key=lambda tup: tup[3], reverse=True)
+
+            results += items[mode]
+
+        return results
+
+    def seed_ratio(self):
+        return self.ratio
+
+
+class HD4FREECache(tvcache.TVCache):
+    def __init__(self, provider_obj):
+
+        tvcache.TVCache.__init__(self, provider_obj)
+
+        # Cache results for 10 min
+        self.minTime = 10
+
+    def _getRSSData(self):
+
+        search_params = {'RSS': ['']}
+        return {'entries': self.provider.search(search_params)}
+
+provider = HD4FREEProvider()
diff --git a/sickbeard/providers/hdbits.py b/sickbeard/providers/hdbits.py
index 72f069d6bc967d39ba16bb4e4d70186ff2a89f71..888e31649df057eed21ff9d8c93791b73bea9594 100644
--- a/sickbeard/providers/hdbits.py
+++ b/sickbeard/providers/hdbits.py
@@ -69,11 +69,7 @@ class HDBitsProvider(TorrentProvider):
         return episode_search_string
 
     def _get_title_and_url(self, item):
-
-        title = item['name']
-        if title:
-            title = self._clean_title(title)
-
+        title = item.get('name', '').replace(' ', '.')
         url = self.urls['download'] + urllib.urlencode({'id': item['id'], 'passkey': self.passkey})
 
         return title, url
diff --git a/sickbeard/providers/hounddawgs.py b/sickbeard/providers/hounddawgs.py
index c5f1fb6202a7b083b2e74c3ac2fc792a455ca6f5..2d2b4a77438ead5f2efebcb9889b5ab3ba162d95 100644
--- a/sickbeard/providers/hounddawgs.py
+++ b/sickbeard/providers/hounddawgs.py
@@ -190,7 +190,7 @@ class HoundDawgsProvider(TorrentProvider):  # pylint: disable=too-many-instance-
         modifier = matches.group(2)
 
         mod = {'K': 1, 'M': 2, 'G': 3, 'T': 4}
-        return float(size) * 1024**mod[modifier]
+        return int(float(size) * 1024**mod[modifier])
 
 
     def seed_ratio(self):
diff --git a/sickbeard/providers/rsstorrent.py b/sickbeard/providers/rsstorrent.py
index dd728c4e7def044e2c807ae79fa6b66fc7f2851d..bb3d72ed7317664fb0580b6ca9201d1ae2c5c0e9 100644
--- a/sickbeard/providers/rsstorrent.py
+++ b/sickbeard/providers/rsstorrent.py
@@ -71,9 +71,7 @@ class TorrentRssProvider(TorrentProvider):
 
     def _get_title_and_url(self, item):
 
-        title = item.get(self.titleTAG)
-        if title:
-            title = self._clean_title(title)
+        title = item.get(self.titleTAG, '').replace(' ', '.')
 
         attempt_list = [lambda: item.get('torrent_magneturi'),
 
diff --git a/sickbeard/search.py b/sickbeard/search.py
index f4b0deaf7e36b4dbbe264b92b70b625059d33896..ab4411f83bbfb87f9cd884402334c30448a692f1 100644
--- a/sickbeard/search.py
+++ b/sickbeard/search.py
@@ -367,7 +367,6 @@ def searchForNeededEpisodes():
     didSearch = False
 
     origThreadName = threading.currentThread().name
-    threads = []
 
     show_list = sickbeard.showList
     fromDate = datetime.date.fromordinal(1)
@@ -375,19 +374,15 @@ def searchForNeededEpisodes():
 
     for curShow in show_list:
         if not curShow.paused:
+            sickbeard.name_cache.buildNameCache(curShow)
             episodes.extend(wantedEpisodes(curShow, fromDate))
 
     providers = [x for x in sickbeard.providers.sortedProviderList(sickbeard.RANDOMIZE_PROVIDERS) if x.is_active() and x.enable_daily]
     for curProvider in providers:
-        threads += [threading.Thread(target=curProvider.cache.updateCache, name=origThreadName + " :: [" + curProvider.name + "]")]
-
-    # start the thread we just created
-    for t in threads:
-        t.start()
+        threading.currentThread().name = origThreadName + " :: [" + curProvider.name + "]"
+        curProvider.cache.updateCache()
 
-    # wait for all threads to finish
-    for t in threads:
-        t.join()
+    threading.currentThread().name = origThreadName
 
     for curProvider in providers:
         threading.currentThread().name = origThreadName + " :: [" + curProvider.name + "]"
@@ -447,7 +442,6 @@ def searchProviders(show, episodes, manualSearch=False, downCurQuality=False):
     finalResults = []
 
     didSearch = False
-    threads = []
 
     # build name cache for show
     sickbeard.name_cache.buildNameCache(show)
@@ -456,18 +450,12 @@ def searchProviders(show, episodes, manualSearch=False, downCurQuality=False):
 
     providers = [x for x in sickbeard.providers.sortedProviderList(sickbeard.RANDOMIZE_PROVIDERS) if x.is_active() and x.enable_backlog]
     for curProvider in providers:
-        threads += [threading.Thread(target=curProvider.cache.updateCache,
-                                     name=origThreadName + " :: [" + curProvider.name + "]")]
-
-    # start the thread we just created
-    for t in threads:
-        t.start()
+        threading.currentThread().name = origThreadName + " :: [" + curProvider.name + "]"
+        curProvider.cache.updateCache()
 
-    # wait for all threads to finish
-    for t in threads:
-        t.join()
+    threading.currentThread().name = origThreadName
 
-    for providerNum, curProvider in enumerate(providers):
+    for curProvider in providers:
         if curProvider.anime_only and not show.is_anime:
             logger.log(u"" + str(show.name) + " is not an anime, skiping", logger.DEBUG)
             continue
diff --git a/sickbeard/subtitles.py b/sickbeard/subtitles.py
index a7fab4ccdc17f97f44658ff57e6286aac2444dc8..932caded5b61b2dd5d53a85f1add51836e67bba8 100644
--- a/sickbeard/subtitles.py
+++ b/sickbeard/subtitles.py
@@ -87,21 +87,19 @@ def sorted_service_list():
     for current_service in sickbeard.SUBTITLES_SERVICES_LIST:
         if current_service in subliminal.provider_manager.names():
             new_list.append({'name': current_service,
-                             'url': PROVIDER_URLS[current_service] if current_service in PROVIDER_URLS else
-                                    lmgtfy % current_service,
+                             'url': PROVIDER_URLS[current_service] if current_service in
+                                    PROVIDER_URLS else lmgtfy % current_service,
                              'image': current_service + '.png',
-                             'enabled': sickbeard.SUBTITLES_SERVICES_ENABLED[current_index] == 1
-                            })
+                             'enabled': sickbeard.SUBTITLES_SERVICES_ENABLED[current_index] == 1})
         current_index += 1
 
     for current_service in subliminal.provider_manager.names():
         if current_service not in [service['name'] for service in new_list]:
             new_list.append({'name': current_service,
-                             'url': PROVIDER_URLS[current_service] if current_service in PROVIDER_URLS else
-                                    lmgtfy % current_service,
+                             'url': PROVIDER_URLS[current_service] if current_service in
+                                    PROVIDER_URLS else lmgtfy % current_service,
                              'image': current_service + '.png',
-                             'enabled': False,
-                            })
+                             'enabled': False})
 
     return new_list
 
@@ -112,7 +110,7 @@ def enabled_service_list():
 
 def wanted_languages(sql_like=None):
     wanted = frozenset(sickbeard.SUBTITLES_LANGUAGES).intersection(subtitle_code_filter())
-    return (wanted, '%' + ','.join(wanted) + '%')[bool(sql_like)]
+    return (wanted, '%' + ','.join(sorted(wanted)) + '%')[bool(sql_like)]
 
 
 def get_needed_languages(subtitles):
@@ -124,13 +122,16 @@ def subtitle_code_filter():
 
 
 def needs_subtitles(subtitles):
-    if isinstance(subtitles, basestring) and sickbeard.SUBTITLES_MULTI:
-        subtitles = {subtitle.strip() for subtitle in subtitles.split(',')}
+    if not wanted_languages():
+        return False
+
+    if isinstance(subtitles, basestring):
+        subtitles = {subtitle.strip() for subtitle in subtitles.split(',') if subtitle.strip()}
 
     if sickbeard.SUBTITLES_MULTI:
-        return len(wanted_languages().difference(subtitles)) > 0
-    elif 'und' not in subtitles:
-        return True
+        return wanted_languages().difference(subtitles)
+
+    return 'und' not in subtitles
 
 
 # Hack around this for now.
@@ -150,7 +151,7 @@ def code_from_code(code):
     return from_code(code).opensubtitles
 
 
-def download_subtitles(subtitles_info):
+def download_subtitles(subtitles_info):  # pylint: disable=too-many-locals
     existing_subtitles = subtitles_info['subtitles']
 
     if not needs_subtitles(existing_subtitles):
@@ -168,6 +169,7 @@ def download_subtitles(subtitles_info):
 
     subtitles_path = get_subtitles_path(subtitles_info['location']).encode(sickbeard.SYS_ENCODING)
     video_path = subtitles_info['location'].encode(sickbeard.SYS_ENCODING)
+    user_score = 132 if sickbeard.SUBTITLES_PERFECT_MATCH else 111
 
     video = get_video(video_path, subtitles_path=subtitles_path)
     if not video:
@@ -195,13 +197,14 @@ def download_subtitles(subtitles_info):
             return (existing_subtitles, None)
 
         for sub in subtitles_list:
-                    matches = sub.get_matches(video, hearing_impaired=False)
-                    score = subliminal.subtitle.compute_score(matches, video)
-                    logger.log(u"[%s] Subtitle score for %s is: %s (min=132)" % (sub.provider_name, sub.id, score), logger.DEBUG)
+            matches = sub.get_matches(video, hearing_impaired=False)
+            score = subliminal.subtitle.compute_score(matches, video)
+            logger.log(u"[%s] Subtitle score for %s is: %s (min=%s)"
+                       % (sub.provider_name, sub.id, score, user_score), logger.DEBUG)
 
-        found_subtitles = pool.download_best_subtitles(subtitles_list, video, languages=languages, min_score=132,
+        found_subtitles = pool.download_best_subtitles(subtitles_list, video, languages=languages,
                                                        hearing_impaired=sickbeard.SUBTITLES_HEARING_IMPAIRED,
-                                                       only_one=not sickbeard.SUBTITLES_MULTI)
+                                                       min_score=user_score, only_one=not sickbeard.SUBTITLES_MULTI)
 
         subliminal.save_subtitles(video, found_subtitles, directory=subtitles_path,
                                   single=not sickbeard.SUBTITLES_MULTI)
@@ -230,8 +233,8 @@ def download_subtitles(subtitles_info):
 
     if sickbeard.SUBTITLES_HISTORY:
         for subtitle in found_subtitles:
-            logger.log(u'history.logSubtitle %s, %s' %
-                       (subtitle.provider_name, subtitle.language.opensubtitles), logger.DEBUG)
+            logger.log(u'history.logSubtitle %s, %s'
+                       % (subtitle.provider_name, subtitle.language.opensubtitles), logger.DEBUG)
 
             history.logSubtitle(subtitles_info['show_indexerid'], subtitles_info['season'],
                                 subtitles_info['episode'], subtitles_info['status'], subtitle)
@@ -311,7 +314,7 @@ class SubtitlesFinder(object):
         self.amActive = False
 
     @staticmethod
-    def subtitles_download_in_pp():  # pylint: disable=too-many-locals
+    def subtitles_download_in_pp():  # pylint: disable=too-many-locals, too-many-branches
         logger.log(u'Checking for needed subtitles in Post-Process folder', logger.INFO)
 
         providers = enabled_service_list()
@@ -340,8 +343,9 @@ class SubtitlesFinder(object):
                         if new_video_filename != video_filename:
                             os.rename(video_filename, new_video_filename)
                             video_filename = new_video_filename
-                    except Exception as e:
-                        logger.log(u'Could not remove non release groups from video file. Error: %r' % ex(e), logger.DEBUG)
+                    except Exception as error:
+                        logger.log(u'Could not remove non release groups from video file. Error: %r'
+                                   % ex(error), logger.DEBUG)
                     if video_filename.rsplit(".", 1)[1] in media_extensions:
                         try:
                             video = subliminal.scan_video(os.path.join(root, video_filename),
@@ -354,14 +358,17 @@ class SubtitlesFinder(object):
                                 continue
 
                             hearing_impaired = sickbeard.SUBTITLES_HEARING_IMPAIRED
+                            user_score = 132 if sickbeard.SUBTITLES_PERFECT_MATCH else 111
                             found_subtitles = pool.download_best_subtitles(subtitles_list, video, languages=languages,
-                                                                           hearing_impaired=hearing_impaired, min_score=132,
+                                                                           hearing_impaired=hearing_impaired,
+                                                                           min_score=user_score,
                                                                            only_one=not sickbeard.SUBTITLES_MULTI)
 
                             for sub in subtitles_list:
-                                        matches = sub.get_matches(video, hearing_impaired=False)
-                                        score = subliminal.subtitle.compute_score(matches, video)
-                                        logger.log(u"[%s] Subtitle score for %s is: %s (min=132)" % (sub.provider_name, sub.id, score), logger.DEBUG)
+                                matches = sub.get_matches(video, hearing_impaired=False)
+                                score = subliminal.subtitle.compute_score(matches, video)
+                                logger.log(u"[%s] Subtitle score for %s is: %s (min=%s)"
+                                           % (sub.provider_name, sub.id, score, user_score), logger.DEBUG)
 
                             downloaded_languages = set()
                             for subtitle in found_subtitles:
@@ -392,7 +399,7 @@ class SubtitlesFinder(object):
                 logger.log(u"Starting post-process with default settings now that we found subtitles")
                 processTV.processDir(sickbeard.TV_DOWNLOAD_DIR)
 
-    def run(self, force=False):  # pylint: disable=unused-argument
+    def run(self, force=False):  # pylint: disable=unused-argument,too-many-statements,too-many-branches
 
         if not sickbeard.USE_SUBTITLES:
             return
@@ -416,24 +423,34 @@ class SubtitlesFinder(object):
         #  - search count < 2 and diff(airdate, now) > 1 week : now -> 1d
         #  - search count < 7 and diff(airdate, now) <= 1 week : now -> 4h -> 8h -> 16h -> 1d -> 1d -> 1d
 
+        """
+        Defines the hours to wait between 2 subtitles search depending on:
+        - the episode: new or old
+        - the number of searches done so far (searchcount), represented by the index of the list
+        """
+        rules = {'old': [0, 24], 'new': [0, 4, 8, 4, 16, 24, 24]}
+
+        if sickbeard.SUBTITLES_MULTI:
+            query_languages = wanted_languages(True)
+        else:
+            query_languages = '%und%'
+
         today = datetime.date.today().toordinal()
         database = db.DBConnection()
-
         sql_results = database.select(
-            'SELECT s.show_name, e.showid, e.season, e.episode, e.status, e.subtitles, ' +
+            'SELECT s.show_name, e.showid, e.season, e.episode, e.status, e.subtitles, '
             'e.subtitles_searchcount AS searchcount, e.subtitles_lastsearch AS lastsearch, e.location, '
-            '(? - e.airdate) AS airdate_daydiff ' +
-            'FROM tv_episodes AS e INNER JOIN tv_shows AS s ON (e.showid = s.indexer_id) ' +
-            'WHERE s.subtitles = 1 AND e.subtitles NOT LIKE (?) ' +
-            'AND (e.subtitles_searchcount <= 2 OR (e.subtitles_searchcount <= 7 AND airdate_daydiff <= 7)) ' +
-            'AND e.location != ""', [today, wanted_languages(True)])
+            '(? - e.airdate) AS airdate_daydiff '
+            'FROM tv_episodes AS e INNER JOIN tv_shows AS s ON (e.showid = s.indexer_id) '
+            'WHERE s.subtitles = 1 AND e.subtitles NOT LIKE ? '
+            'AND (e.subtitles_searchcount <= 2 OR (e.subtitles_searchcount <= 7 AND airdate_daydiff <= 7)) '
+            'AND e.location != ""', [today, query_languages])
 
         if len(sql_results) == 0:
             logger.log(u'No subtitles to download', logger.INFO)
             self.amActive = False
             return
 
-        rules = self._get_rules()
         now = datetime.datetime.now()
         for ep_to_sub in sql_results:
 
@@ -447,58 +464,56 @@ class SubtitlesFinder(object):
                            % (ep_to_sub['show_name'], ep_to_sub['season'], ep_to_sub['episode']), logger.DEBUG)
                 continue
 
-            # http://bugs.python.org/issue7980#msg221094
-            # I dont think this needs done here, but keeping to be safe (Recent shows rule)
-            datetime.datetime.strptime('20110101', '%Y%m%d')
-            if ((ep_to_sub['airdate_daydiff'] > 7 and ep_to_sub['searchcount'] < 2 and
-                 now - datetime.datetime.strptime(ep_to_sub['lastsearch'], dateTimeFormat) >
-                 datetime.timedelta(hours=rules['old'][ep_to_sub['searchcount']])) or
-                    (ep_to_sub['airdate_daydiff'] <= 7 and ep_to_sub['searchcount'] < 7 and
-                     now - datetime.datetime.strptime(ep_to_sub['lastsearch'], dateTimeFormat) >
-                     datetime.timedelta(hours=rules['new'][ep_to_sub['searchcount']]))):
-
-                logger.log(u'Downloading subtitles for %s S%02dE%02d'
-                           % (ep_to_sub['show_name'], ep_to_sub['season'], ep_to_sub['episode']), logger.DEBUG)
+            logger.log(u"%s S%02dE%02d doesn't have all needed subtitles"
+                       % (ep_to_sub['show_name'], ep_to_sub['season'], ep_to_sub['episode']), logger.DEBUG)
+
+            try:
+                try:
+                    lastsearched = datetime.datetime.strptime(ep_to_sub['lastsearch'], dateTimeFormat)
+                except ValueError:
+                    lastsearched = datetime.datetime.min
 
-                show_object = Show.find(sickbeard.showList, int(ep_to_sub['showid']))
-                if not show_object:
-                    logger.log(u'Show with ID %s not found in the database' % ep_to_sub['showid'], logger.DEBUG)
-                    self.amActive = False
-                    return
+                if ((ep_to_sub['airdate_daydiff'] > 7 and ep_to_sub['searchcount'] < 2 and
+                     now - lastsearched > datetime.timedelta(hours=rules['old'][ep_to_sub['searchcount']])) or
+                        (ep_to_sub['airdate_daydiff'] <= 7 and ep_to_sub['searchcount'] < 7 and
+                         now - lastsearched > datetime.timedelta(hours=rules['new'][ep_to_sub['searchcount']]))):
 
-                episode_object = show_object.getEpisode(int(ep_to_sub["season"]), int(ep_to_sub["episode"]))
-                if isinstance(episode_object, str):
-                    logger.log(u'%s S%02dE%02d not found in the database'
-                               % (ep_to_sub['show_name'], ep_to_sub['season'], ep_to_sub['episode']), logger.DEBUG)
-                    self.amActive = False
-                    return
+                    logger.log(u'Started subtitles search for %s S%02dE%02d'
+                               % (ep_to_sub['show_name'], ep_to_sub['season'], ep_to_sub['episode']), logger.INFO)
 
-                existing_subtitles = episode_object.subtitles
+                    show_object = Show.find(sickbeard.showList, int(ep_to_sub['showid']))
+                    if not show_object:
+                        logger.log(u'Show with ID %s not found in the database' % ep_to_sub['showid'], logger.DEBUG)
+                        continue
 
-                try:
-                    episode_object.download_subtitles()
-                except Exception as error:
-                    logger.log(u'Unable to find subtitles for %s S%02dE%02d'
-                               % (ep_to_sub['show_name'], ep_to_sub['season'], ep_to_sub['episode']), logger.DEBUG)
-                    logger.log(str(error), logger.DEBUG)
-                    self.amActive = False
-                    return
-
-                new_subtitles = frozenset(episode_object.subtitles).difference(existing_subtitles)
-                if new_subtitles:
-                    logger.log(u'Downloaded %s subtitles for %s S%02dE%02d'
-                               % (', '.join(new_subtitles), ep_to_sub['show_name'], ep_to_sub["season"], ep_to_sub["episode"]))
+                    episode_object = show_object.getEpisode(int(ep_to_sub["season"]), int(ep_to_sub["episode"]))
+                    if isinstance(episode_object, str):
+                        logger.log(u'%s S%02dE%02d not found in the database'
+                                   % (ep_to_sub['show_name'], ep_to_sub['season'], ep_to_sub['episode']), logger.DEBUG)
+                        continue
 
-        self.amActive = False
+                    existing_subtitles = episode_object.subtitles
 
-    @staticmethod
-    def _get_rules():
-        """
-        Define the hours to wait between 2 subtitles search depending on:
-        - the episode: new or old
-        - the number of searches done so far (searchcount), represented by the index of the list
-        """
-        return {'old': [0, 24], 'new': [0, 4, 8, 4, 16, 24, 24]}
+                    try:
+                        episode_object.download_subtitles()
+                    except Exception as error:
+                        logger.log(u'Unable to find subtitles for %s S%02dE%02d. Error: %r'
+                                   % (ep_to_sub['show_name'], ep_to_sub['season'], ep_to_sub['episode'],
+                                      ex(error)), logger.ERROR)
+                        continue
+
+                    new_subtitles = frozenset(episode_object.subtitles).difference(existing_subtitles)
+                    if new_subtitles:
+                        logger.log(u'Downloaded %s subtitles for %s S%02dE%02d'
+                                   % (', '.join(new_subtitles), ep_to_sub['show_name'],
+                                      ep_to_sub["season"], ep_to_sub["episode"]))
+            except Exception as error:
+                logger.log(u'Error while searching subtitles for %s S%02dE%02d. Error: %r'
+                           % (ep_to_sub['show_name'], ep_to_sub['season'], ep_to_sub['episode'],
+                              ex(error)), logger.ERROR)
+                continue
+
+        self.amActive = False
 
 
 def run_subs_extra_scripts(episode_object, found_subtitles, video, single=False):
diff --git a/sickbeard/versionChecker.py b/sickbeard/versionChecker.py
index 414fb08911a229a412aa716588301d882f67014a..40ee5ffe0bd8289ee7e7b21380e413f719f17f87 100644
--- a/sickbeard/versionChecker.py
+++ b/sickbeard/versionChecker.py
@@ -686,7 +686,7 @@ class GitUpdateManager(UpdateManager):
     def update_remote_origin(self):
         self._run_git(self._git_path, 'config remote.%s.url %s' % (sickbeard.GIT_REMOTE, sickbeard.GIT_REMOTE_URL))
         if sickbeard.GIT_USERNAME:
-            self._run_git(self._git_path, 'config remote.%s.pushurl %s' % (sickbeard.GIT_REMOTE, sickbeard.GIT_REMOTE_URL.replace(sickbeard.GIT_ORG, sickbeard.GIT_USERNAME)))
+            self._run_git(self._git_path, 'config remote.%s.pushurl %s' % (sickbeard.GIT_REMOTE, sickbeard.GIT_REMOTE_URL.replace(sickbeard.GIT_ORG, sickbeard.GIT_USERNAME, 1)))
 
 class SourceUpdateManager(UpdateManager):
     def __init__(self):
diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py
index 69e9ed15461e1b8dc2ded14b8d92ebb886e97db9..8f6d76f65bfe6a72895d4a93d5abbdc77041bfca 100644
--- a/sickbeard/webserve.py
+++ b/sickbeard/webserve.py
@@ -4091,9 +4091,12 @@ class ConfigPostProcessing(Config):
         sickbeard.FILE_TIMESTAMP_TIMEZONE = file_timestamp_timezone
         sickbeard.MOVE_ASSOCIATED_FILES = config.checkbox_to_value(move_associated_files)
         sickbeard.SYNC_FILES = sync_files
-        sickbeard.ALLOWED_EXTENSIONS = allowed_extensions
         sickbeard.POSTPONE_IF_SYNC_FILES = config.checkbox_to_value(postpone_if_sync_files)
         sickbeard.POSTPONE_IF_NO_SUBS = config.checkbox_to_value(postpone_if_no_subs)
+        # If 'postpone if no subs' is enabled, we must have SRT in allowed extensions list
+        if sickbeard.POSTPONE_IF_NO_SUBS:
+            allowed_extensions += ',srt'
+        sickbeard.ALLOWED_EXTENSIONS = ','.join({x.strip() for x in allowed_extensions.split(',') if x.strip()})
         sickbeard.NAMING_CUSTOM_ABD = config.checkbox_to_value(naming_custom_abd)
         sickbeard.NAMING_CUSTOM_SPORTS = config.checkbox_to_value(naming_custom_sports)
         sickbeard.NAMING_CUSTOM_ANIME = config.checkbox_to_value(naming_custom_anime)
@@ -4966,7 +4969,7 @@ class ConfigSubtitles(Config):
                         header='Subtitles', topmenu='config',
                         controller="config", action="subtitles")
 
-    def saveSubtitles(self, use_subtitles=None, subtitles_plugins=None, subtitles_languages=None, subtitles_dir=None,
+    def saveSubtitles(self, use_subtitles=None, subtitles_plugins=None, subtitles_languages=None, subtitles_dir=None, subtitles_perfect_match=None,
                       service_order=None, subtitles_history=None, subtitles_finder_frequency=None, subtitles_download_in_pp=None,
                       subtitles_multi=None, embedded_subtitles_all=None, subtitles_extra_scripts=None, subtitles_hearing_impaired=None,
                       addic7ed_user=None, addic7ed_pass=None, legendastv_user=None, legendastv_pass=None, opensubtitles_user=None, opensubtitles_pass=None):
@@ -4978,6 +4981,7 @@ class ConfigSubtitles(Config):
 
         sickbeard.SUBTITLES_LANGUAGES = [code.strip() for code in subtitles_languages.split(',') if code.strip() in subtitles.subtitle_code_filter()] if subtitles_languages else []
         sickbeard.SUBTITLES_DIR = subtitles_dir
+        sickbeard.SUBTITLES_PERFECT_MATCH = config.checkbox_to_value(subtitles_perfect_match)
         sickbeard.SUBTITLES_HISTORY = config.checkbox_to_value(subtitles_history)
         sickbeard.EMBEDDED_SUBTITLES_ALL = config.checkbox_to_value(embedded_subtitles_all)
         sickbeard.SUBTITLES_HEARING_IMPAIRED = config.checkbox_to_value(subtitles_hearing_impaired)
diff --git a/tests/api_v1_tests.py b/tests/api_v1_tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..271bdcd8153389dc09acf9e0b88487fe0fb4d4c2
--- /dev/null
+++ b/tests/api_v1_tests.py
@@ -0,0 +1,257 @@
+# coding=utf-8
+
+"""
+Test the SR API
+"""
+
+import unittest
+
+
+class APITestEpisodes(unittest.TestCase):
+    """
+    Test episode commands for API
+
+    This tests all episode related commands from the legacy api.
+    """
+    @unittest.skip('Not yet implemented')
+    def test_episode(self):
+        """
+        Test getting detailed information about an episode using the legacy API.
+
+        :return: None
+        """
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_episode_search(self):
+        """
+        Test searching for an episode using the legacy API.
+        The response might take some time.
+        :return: None
+        """
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_episode_set_status(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_episode_subtitle_search(self):
+        pass
+
+
+class APITestShows(unittest.TestCase):
+    """
+    Test shows commands for API
+    """
+    @unittest.skip('Not yet implemented')
+    def test_shows(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_shows_stats(self):
+        pass
+
+
+class APITestShow(unittest.TestCase):
+    """
+    Test show commands for API
+    """
+    @unittest.skip('Not yet implemented')
+    def test_show(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_show_add_existing(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_show_add_new(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_show_cache(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_show_delete(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_show_get_banner(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_show_get_fanart(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_show_get_network_logo(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_show_get_poster(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_show_get_quality(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_show_pause(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_show_refresh(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_show_season_list(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_show_seasons(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_show_set_quality(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_show_stats(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_show_update(self):
+        pass
+
+
+class APITestSickBeard(unittest.TestCase):
+    @unittest.skip('Not yet implemented')
+    def test_sickbeard(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_sb_add_root_dir(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_sb_check_scheduler(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_sb_check_version(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_sb_delete_root_dir(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_sb_get_defaults(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_sb_get_messages(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_sb_get_root_dirs(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_sb_pause_backlog(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_sb_ping(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_sb_restart(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_sb_search_indexers(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_sb_search_tvdb(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_sb_search_tvrage(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_sb_set_defaults(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_sb_shutdown(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_sb_update(self):
+        pass
+
+
+class APITestHistory(unittest.TestCase):
+    @unittest.skip('Not yet implemented')
+    def test_history(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_history_clear(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_history_trim(self):
+        pass
+
+
+class APITestMisc(unittest.TestCase):
+    @unittest.skip('Not yet implemented')
+    def test_backlog(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_exceptions(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_failed(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_future(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_help(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_logs(self):
+        pass
+
+    @unittest.skip('Not yet implemented')
+    def test_post_process(self):
+        pass
+
+
+TEST_CLASSES = {
+    APITestEpisodes, APITestHistory, APITestMisc, APITestShow, APITestShows, APITestSickBeard
+}
+
+
+def run_all():
+    """
+    Run all tests
+    :return:
+    """
+    unittest.main(verbosity=2)
+
+
+if __name__ == '__main__':
+    run_all()
diff --git a/tests/ssl_sni_tests.py b/tests/ssl_sni_tests.py
index 1d69b96314025c85b559a9ce04782ce6df0130c3..add4e704002c873d53c440416bd4c4e3a8138e53 100644
--- a/tests/ssl_sni_tests.py
+++ b/tests/ssl_sni_tests.py
@@ -36,34 +36,49 @@ import requests  # pylint: disable=import-error
 import sickbeard.providers as providers
 
 
-class SniTests(unittest.TestCase):
-    """
-    Test SNI
+def test_generator(_provider):
     """
-    self_signed_cert_providers = ["Womble's Index", "Libertalia"]
+    Generate tests for each provider
 
-    def test_sni_urls(self):
+    :param test_strings: to generate tests from
+    :return: test
+    """
+    def _connectivity_test(self):  # pylint: disable=unused-argument
         """
-        Test SNI urls
-        :return:
+        Generate tests
+        :param self:
+        :return: test to run
         """
-        print ''
-        # Just checking all providers - we should make this error on non-existent urls.
-        for provider in [provider for provider in providers.makeProviderList() if provider.name not in self.self_signed_cert_providers]:
-            print 'Checking %s' % provider.name
-            try:
-                requests.head(provider.url, verify=certifi.old_where(), timeout=10)
-            except requests.exceptions.Timeout:
-                pass
-            except requests.exceptions.SSLError as error:
-                if u'SSL3_GET_SERVER_CERTIFICATE' not in ex(error):
-                    print 'SSLError on %s: %s' % (provider.name, ex(error.message))
-                    raise
-                else:
-                    print 'Cannot verify certificate for %s' % provider.name
-            except Exception:  # pylint: disable=broad-except
-                pass
+        if not _provider.url:
+            print '%s has no url set, skipping' % _provider.name
+            return
+
+        try:
+            requests.head(_provider.url, verify=certifi.old_where(), timeout=10)
+        except requests.exceptions.SSLError as error:
+            if 'certificate verify failed' in str(error):
+                print 'Cannot verify certificate for %s' % _provider.name
+            else:
+                print 'SSLError on %s: %s' % (_provider.name, ex(error.message))
+                raise
+        except requests.exceptions.Timeout:
+            print 'Provider timed out'
+
+    return _connectivity_test
+
+class SniTests(unittest.TestCase):
+    pass
 
 if __name__ == "__main__":
+    print("==================")
+    print("STARTING - Provider Connectivity TESTS and SSL/SNI")
+    print("==================")
+    print("######################################################################")
+    # Just checking all providers - we should make this error on non-existent urls.
+    for provider in [p for p in providers.makeProviderList()]:
+        test_name = 'test_%s' % provider.name
+        test = test_generator(provider)
+        setattr(SniTests, test_name, test)
+
     SUITE = unittest.TestLoader().loadTestsFromTestCase(SniTests)
     unittest.TextTestRunner(verbosity=2).run(SUITE)