From a1d227666836b58f798a1ba57089e89f70c0e3d5 Mon Sep 17 00:00:00 2001 From: Ruud Date: Thu, 12 Sep 2013 11:07:49 +0200 Subject: [PATCH 01/42] Match variable name in ubuntu init. fix #2149 --- init/ubuntu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/init/ubuntu b/init/ubuntu index 511d9ee8..7f770a67 100644 --- a/init/ubuntu +++ b/init/ubuntu @@ -52,7 +52,7 @@ APP_PATH=${CP_HOME-/opt/couchpotato/} DATA_DIR=${CP_DATA-/var/couchpotato} # Path to store PID file -PID_FILE=${CP_PID_FILE-/var/run/couchpotato.pid} +PID_FILE=${CP_PIDFILE-/var/run/couchpotato.pid} # path to python bin DAEMON=${PYTHON_BIN-/usr/bin/python} From 82d31d996d66e728c382a236555138dfab19e413 Mon Sep 17 00:00:00 2001 From: Ruud Date: Thu, 12 Sep 2013 22:29:59 +0200 Subject: [PATCH 02/42] Set order changes on each run. fix #2148 --- couchpotato/core/providers/info/themoviedb/main.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/couchpotato/core/providers/info/themoviedb/main.py b/couchpotato/core/providers/info/themoviedb/main.py index 387355f8..b9176153 100644 --- a/couchpotato/core/providers/info/themoviedb/main.py +++ b/couchpotato/core/providers/info/themoviedb/main.py @@ -1,6 +1,5 @@ from couchpotato.core.event import addEvent -from couchpotato.core.helpers.encoding import simplifyString, toUnicode, ss -from couchpotato.core.helpers.variable import md5 +from couchpotato.core.helpers.encoding import simplifyString, toUnicode from couchpotato.core.logger import CPLog from couchpotato.core.providers.info.base import MovieProvider import tmdb3 @@ -132,8 +131,6 @@ class TheMovieDb(MovieProvider): if alt_name and not alt_name in movie_data['titles'] and alt_name.lower() != 'none' and alt_name is not None: movie_data['titles'].append(alt_name) - movie_data['titles'] = list(set(movie_data['titles'])) - # Cache movie parsed self.setCache(cache_key, movie_data) @@ -143,7 +140,7 @@ class TheMovieDb(MovieProvider): image_url = '' try: - image_url = getattr(movie, type).geturl(size='original') + image_url = getattr(movie, type).geturl(size = 'original') except: log.debug('Failed getting %s.%s for "%s"', (type, size, movie.title)) From b83b2453a00e56bc6789308c0891690e94109a9a Mon Sep 17 00:00:00 2001 From: Ruud Date: Thu, 12 Sep 2013 22:50:08 +0200 Subject: [PATCH 03/42] not in --- couchpotato/core/providers/info/themoviedb/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/providers/info/themoviedb/main.py b/couchpotato/core/providers/info/themoviedb/main.py index b9176153..376ddad0 100644 --- a/couchpotato/core/providers/info/themoviedb/main.py +++ b/couchpotato/core/providers/info/themoviedb/main.py @@ -128,7 +128,7 @@ class TheMovieDb(MovieProvider): movie_data['titles'].append(movie.originaltitle) for alt in movie.alternate_titles: alt_name = alt.title - if alt_name and not alt_name in movie_data['titles'] and alt_name.lower() != 'none' and alt_name is not None: + if alt_name and alt_name not in movie_data['titles'] and alt_name.lower() != 'none' and alt_name is not None: movie_data['titles'].append(alt_name) # Cache movie parsed From 6b4e4fd440d81175331b72c7aabe055af6b00d99 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 14 Sep 2013 11:41:16 +0200 Subject: [PATCH 04/42] Only show login when both username and password are filled in. fix #2157 --- couchpotato/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/__init__.py b/couchpotato/__init__.py index 04756fa4..b8aa3ab9 100644 --- a/couchpotato/__init__.py +++ b/couchpotato/__init__.py @@ -22,7 +22,7 @@ class BaseHandler(RequestHandler): username = Env.setting('username') password = Env.setting('password') - if username or password: + if username and password: return self.get_secure_cookie('user') else: # Login when no username or password are set return True From 70f834d9256cf0896b019ba6e5d582935aaa4dd8 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 15 Sep 2013 00:46:39 +0200 Subject: [PATCH 05/42] Gilles de la Tourette --- couchpotato/core/media/movie/searcher/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/media/movie/searcher/main.py b/couchpotato/core/media/movie/searcher/main.py index fdf3460d..a8fca185 100644 --- a/couchpotato/core/media/movie/searcher/main.py +++ b/couchpotato/core/media/movie/searcher/main.py @@ -308,7 +308,7 @@ class MovieSearcher(SearcherBase, MovieTypeBase): return False # Ignore porn stuff - pron_tags = ['xxx', 'sex', 'anal', 'tits', 'fuck', 'porn', 'orgy', 'milf', 'boobs', 'erotica', 'erotic'] + pron_tags = ['xxx', 'sex', 'anal', 'tits', 'fuck', 'porn', 'orgy', 'milf', 'boobs', 'erotica', 'erotic', 'cock', 'dick'] pron_words = list(set(nzb_words) & set(pron_tags) - set(movie_words)) if pron_words: log.info('Wrong: %s, probably pr0n', (nzb['name'])) From cfa89c8921a206fc7c13778132a1332c6f513b28 Mon Sep 17 00:00:00 2001 From: mano3m <-> Date: Sun, 15 Sep 2013 01:19:22 +0200 Subject: [PATCH 06/42] [uTorrent] Guarantee a folder uTorrent does not create a folder in case only one file is present in the torrent. This is a workaround that detects torrents with one file. It then removes the torrent and readds it with a specified subfolder. --- couchpotato/core/downloaders/utorrent/main.py | 49 +++++++++++++++++-- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/couchpotato/core/downloaders/utorrent/main.py b/couchpotato/core/downloaders/utorrent/main.py index d9330076..ce82c8c2 100644 --- a/couchpotato/core/downloaders/utorrent/main.py +++ b/couchpotato/core/downloaders/utorrent/main.py @@ -77,7 +77,7 @@ class uTorrent(Downloader): else: info = bdecode(filedata)["info"] torrent_hash = sha1(benc(info)).hexdigest().upper() - torrent_filename = self.createFileName(data, filedata, movie) + torrent_filename = self.createFileName(data, filedata, movie) if data.get('seed_ratio'): torrent_params['seed_override'] = 1 @@ -93,7 +93,7 @@ class uTorrent(Downloader): # Send request to uTorrent if data.get('protocol') == 'torrent_magnet': - self.utorrent_api.add_torrent_uri(data.get('url')) + self.utorrent_api.add_torrent_uri(torrent_filename, data.get('url')) else: self.utorrent_api.add_torrent_file(torrent_filename, filedata) @@ -102,6 +102,39 @@ class uTorrent(Downloader): if self.conf('paused', default = 0): self.utorrent_api.pause_torrent(torrent_hash) + count = 0 + while True: + + count += 1 + # Check if torrent is saved in subfolder of torrent name + data = self.utorrent_api.get_files(torrent_hash) + + torrent_files = json.loads(data) + if torrent_files.get('error'): + log.error('Error getting data from uTorrent: %s', torrent_files.get('error')) + return False + + if (torrent_files.get('files') and len(torrent_files['files'][1]) > 0) or count > 60: + break + + time.sleep(1) + + # Torrent has only one file, so uTorrent wont create a folder for it + if len(torrent_files['files'][1]) == 1: + # Remove torrent and try again + self.utorrent_api.remove_torrent(torrent_hash, remove_data = True) + + # Send request to uTorrent + if data.get('protocol') == 'torrent_magnet': + self.utorrent_api.add_torrent_uri(torrent_filename, data.get('url'), add_folder = True) + else: + self.utorrent_api.add_torrent_file(torrent_filename, filedata, add_folder = True) + + # Change settings of added torrent + self.utorrent_api.set_torrent(torrent_hash, torrent_params) + if self.conf('paused', default = 0): + self.utorrent_api.pause_torrent(torrent_hash) + return self.downloadReturnId(torrent_hash) def getAllDownloadStatus(self): @@ -224,12 +257,16 @@ class uTorrentAPI(object): token = re.findall("(.*?) Date: Mon, 16 Sep 2013 23:12:46 +1000 Subject: [PATCH 07/42] Fix Deluge SSL negotiation errors on Windows machines. --- libs/synchronousdeluge/transfer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/synchronousdeluge/transfer.py b/libs/synchronousdeluge/transfer.py index 29b1ebef..a86e1451 100644 --- a/libs/synchronousdeluge/transfer.py +++ b/libs/synchronousdeluge/transfer.py @@ -19,7 +19,7 @@ class DelugeTransfer(object): self.disconnect() self.sock = socket.create_connection(hostport) - self.conn = ssl.wrap_socket(self.sock) + self.conn = ssl.wrap_socket(self.sock, None, None, False, ssl.CERT_NONE, ssl.SSLv3) self.connected = True def disconnect(self): From 9211e608041f95e0e3223efff6b5f33f2050190b Mon Sep 17 00:00:00 2001 From: Techmunk Date: Tue, 17 Sep 2013 00:06:35 +1000 Subject: [PATCH 08/42] Use the actual SSLv3 constant in deluge transfer.py. --- libs/synchronousdeluge/transfer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/synchronousdeluge/transfer.py b/libs/synchronousdeluge/transfer.py index a86e1451..979ffb16 100644 --- a/libs/synchronousdeluge/transfer.py +++ b/libs/synchronousdeluge/transfer.py @@ -19,7 +19,7 @@ class DelugeTransfer(object): self.disconnect() self.sock = socket.create_connection(hostport) - self.conn = ssl.wrap_socket(self.sock, None, None, False, ssl.CERT_NONE, ssl.SSLv3) + self.conn = ssl.wrap_socket(self.sock, None, None, False, ssl.CERT_NONE, ssl.PROTOCOL_SSLv3) self.connected = True def disconnect(self): From 074005ed0239f421bae0cfba43bf34bbed8f2df0 Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 16 Sep 2013 22:33:26 +0200 Subject: [PATCH 09/42] Use existing category on re-add. fix #2182 --- couchpotato/core/media/movie/_base/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/media/movie/_base/main.py b/couchpotato/core/media/movie/_base/main.py index e6f66e26..310b4e92 100644 --- a/couchpotato/core/media/movie/_base/main.py +++ b/couchpotato/core/media/movie/_base/main.py @@ -469,7 +469,7 @@ class MovieBase(MovieTypeBase): fireEvent('release.delete', release.id, single = True) m.profile_id = params.get('profile_id', default_profile.get('id')) - m.category_id = tryInt(cat_id) if cat_id is not None and tryInt(cat_id) > 0 else None + m.category_id = tryInt(cat_id) if cat_id is not None and tryInt(cat_id) > 0 else (m.category_id or None) else: log.debug('Movie already exists, not updating: %s', params) added = False From 08a1e1e582298dc91f03bf799dc72be2db125cfd Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 16 Sep 2013 22:33:45 +0200 Subject: [PATCH 10/42] Done use faulty None value for category --- couchpotato/core/plugins/renamer/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/plugins/renamer/main.py b/couchpotato/core/plugins/renamer/main.py index 4bbf351a..0013298a 100644 --- a/couchpotato/core/plugins/renamer/main.py +++ b/couchpotato/core/plugins/renamer/main.py @@ -174,7 +174,7 @@ class Renamer(Plugin): # Overwrite destination when set in category destination = self.conf('to') for movie in library_ent.movies: - if movie.category and movie.category.destination and len(movie.category.destination) > 0: + if movie.category and movie.category.destination and len(movie.category.destination) > 0 and movie.category.destination != 'None': destination = movie.category.destination log.debug('Setting category destination for "%s": %s' % (movie_title, destination)) else: From 18e3194e27cb160480ab2bd0bbb98ba290580312 Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 16 Sep 2013 22:37:10 +0200 Subject: [PATCH 11/42] Better category defaults --- couchpotato/core/plugins/category/main.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/couchpotato/core/plugins/category/main.py b/couchpotato/core/plugins/category/main.py index 6a60ae4e..d13a74a3 100644 --- a/couchpotato/core/plugins/category/main.py +++ b/couchpotato/core/plugins/category/main.py @@ -54,12 +54,11 @@ class CategoryPlugin(Plugin): db.add(c) c.order = kwargs.get('order', c.order if c.order else 0) - c.label = toUnicode(kwargs.get('label')) - c.path = toUnicode(kwargs.get('path')) - c.ignored = toUnicode(kwargs.get('ignored')) - c.preferred = toUnicode(kwargs.get('preferred')) - c.required = toUnicode(kwargs.get('required')) - c.destination = toUnicode(kwargs.get('destination')) + c.label = toUnicode(kwargs.get('label', '')) + c.ignored = toUnicode(kwargs.get('ignored', '')) + c.preferred = toUnicode(kwargs.get('preferred', '')) + c.required = toUnicode(kwargs.get('required', '')) + c.destination = toUnicode(kwargs.get('destination', '')) db.commit() From a092f394fa6ab60506103707b38ddd7b26f8c987 Mon Sep 17 00:00:00 2001 From: Ruud Date: Tue, 17 Sep 2013 20:18:41 +0200 Subject: [PATCH 12/42] Snatch next didn't pick correct element --- .../media/movie/_base/static/movie.actions.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/couchpotato/core/media/movie/_base/static/movie.actions.js b/couchpotato/core/media/movie/_base/static/movie.actions.js index 7d8c37fd..24bf6260 100644 --- a/couchpotato/core/media/movie/_base/static/movie.actions.js +++ b/couchpotato/core/media/movie/_base/static/movie.actions.js @@ -260,27 +260,30 @@ MA.Release = new Class({ if(self.next_release || (self.last_release && ['ignored', 'failed'].indexOf(self.last_release.status.identifier) === false)){ self.trynext_container = new Element('div.buttons.try_container').inject(self.release_container, 'top'); + + var nr = self.next_release, + lr = self.last_release; self.trynext_container.adopt( new Element('span.or', { 'text': 'This movie is snatched, if anything went wrong, download' }), - self.last_release ? new Element('a.button.orange', { + lr ? new Element('a.button.orange', { 'text': 'the same release again', 'events': { 'click': function(){ - self.download(self.last_release); + self.download(lr); } } }) : null, - self.next_release && self.last_release ? new Element('span.or', { + nr && lr ? new Element('span.or', { 'text': ',' }) : null, - self.next_release ? [new Element('a.button.green', { - 'text': self.last_release ? 'another release' : 'the best release', + nr ? [new Element('a.button.green', { + 'text': lr ? 'another release' : 'the best release', 'events': { 'click': function(){ - self.download(self.next_release); + self.download(nr); } } }), @@ -371,8 +374,10 @@ MA.Release = new Class({ 'onComplete': function(json){ icon.removeClass('icon spinner'); - if(json.success) + if(json.success){ icon.addClass('completed'); + release_el.getElement('.release_status').set('text', 'snatched'); + } else icon.addClass('attention').set('title', 'Something went wrong when downloading, please check logs.'); } From 821c26f35bb66a1e949a78c59c2c9df81bf54444 Mon Sep 17 00:00:00 2001 From: Ruud Date: Tue, 17 Sep 2013 20:39:20 +0200 Subject: [PATCH 13/42] Return default cached suggestion list. fix #2191 --- couchpotato/core/plugins/suggestion/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/plugins/suggestion/main.py b/couchpotato/core/plugins/suggestion/main.py index 2cedeba6..a7d0f82a 100644 --- a/couchpotato/core/plugins/suggestion/main.py +++ b/couchpotato/core/plugins/suggestion/main.py @@ -73,7 +73,7 @@ class Suggestion(Plugin): def updateSuggestionCache(self, ignore_imdb = None, limit = 6, ignored = None, seen = None): # Combine with previous suggestion_cache - cached_suggestion = self.getCache('suggestion_cached') + cached_suggestion = self.getCache('suggestion_cached') or [] new_suggestions = [] ignored = [] if not ignored else ignored seen = [] if not seen else seen From 156da670e871be7c79a99c8d28561e6a51239a1e Mon Sep 17 00:00:00 2001 From: Ruud Date: Tue, 17 Sep 2013 20:43:41 +0200 Subject: [PATCH 14/42] Encode before checking imdb content. fix #2186 --- couchpotato/core/helpers/variable.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/couchpotato/core/helpers/variable.py b/couchpotato/core/helpers/variable.py index 8f393d0a..fd1a101d 100644 --- a/couchpotato/core/helpers/variable.py +++ b/couchpotato/core/helpers/variable.py @@ -1,4 +1,4 @@ -from couchpotato.core.helpers.encoding import simplifyString, toSafeString +from couchpotato.core.helpers.encoding import simplifyString, toSafeString, ss from couchpotato.core.logger import CPLog import hashlib import os.path @@ -125,6 +125,8 @@ def cleanHost(host): def getImdb(txt, check_inside = True, multiple = False): + txt = ss(txt) + if check_inside and os.path.isfile(txt): output = open(txt, 'r') txt = output.read() From 5f5f17112a8b10ddcbcbb2cf2ea4ae1b15b52a86 Mon Sep 17 00:00:00 2001 From: Ruud Date: Tue, 17 Sep 2013 20:48:01 +0200 Subject: [PATCH 15/42] Don't try to search SceneAccess for BR-Disk. fix #2188 --- couchpotato/core/providers/torrent/sceneaccess/main.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/couchpotato/core/providers/torrent/sceneaccess/main.py b/couchpotato/core/providers/torrent/sceneaccess/main.py index b2d3dd68..7e9ab896 100644 --- a/couchpotato/core/providers/torrent/sceneaccess/main.py +++ b/couchpotato/core/providers/torrent/sceneaccess/main.py @@ -29,9 +29,13 @@ class SceneAccess(TorrentProvider): def _search(self, movie, quality, results): + cat = self.getCatId(quality['identifier']) + if not cat: + return + url = self.urls['search'] % ( - self.getCatId(quality['identifier'])[0], - self.getCatId(quality['identifier'])[0] + cat[0], + cat[0] ) arguments = tryUrlencode({ From ad01a3da4d47192d8405075de7c114e9e1a84e66 Mon Sep 17 00:00:00 2001 From: Ruud Date: Tue, 17 Sep 2013 21:04:15 +0200 Subject: [PATCH 16/42] Update GuessIt --- libs/guessit/__init__.py | 35 ++++++---- libs/guessit/fileutils.py | 6 +- libs/guessit/guess.py | 2 +- libs/guessit/language.py | 2 +- libs/guessit/matcher.py | 6 +- libs/guessit/matchtree.py | 2 +- libs/guessit/patterns.py | 50 +++++++++----- libs/guessit/transfo/guess_episodes_rexps.py | 18 +++-- libs/guessit/transfo/guess_idnumber.py | 71 ++++++++++++++++++++ libs/guessit/transfo/guess_release_group.py | 21 ++++-- libs/guessit/transfo/guess_year.py | 16 ++++- 11 files changed, 179 insertions(+), 50 deletions(-) create mode 100755 libs/guessit/transfo/guess_idnumber.py diff --git a/libs/guessit/__init__.py b/libs/guessit/__init__.py index 386aa7f4..ce140248 100755 --- a/libs/guessit/__init__.py +++ b/libs/guessit/__init__.py @@ -20,7 +20,7 @@ from __future__ import unicode_literals -__version__ = '0.6-dev' +__version__ = '0.7-dev' __all__ = ['Guess', 'Language', 'guess_file_info', 'guess_video_info', 'guess_movie_info', 'guess_episode_info'] @@ -91,7 +91,28 @@ log.addHandler(h) def _guess_filename(filename, filetype): + def find_nodes(tree, props): + """Yields all nodes containing any of the given props.""" + if isinstance(props, base_text_type): + props = [props] + for node in tree.nodes(): + if any(prop in node.guess for prop in props): + yield node + + def warning(title): + log.warning('%s, guesses: %s - %s' % (title, m.nice_string(), m2.nice_string())) + return m + mtree = IterativeMatcher(filename, filetype=filetype) + + # if there are multiple possible years found, we assume the first one is + # part of the title, reparse the tree taking this into account + years = set(n.value for n in find_nodes(mtree.match_tree, 'year')) + if len(years) >= 2: + mtree = IterativeMatcher(filename, filetype=filetype, + opts=['skip_first_year']) + + m = mtree.matched() if 'language' not in m and 'subtitleLanguage' not in m: @@ -102,20 +123,10 @@ def _guess_filename(filename, filetype): opts=['nolanguage', 'nocountry']) m2 = mtree2.matched() - def find_nodes(tree, props): - """Yields all nodes containing any of the given props.""" - if isinstance(props, base_text_type): - props = [props] - for node in tree.nodes(): - if any(prop in node.guess for prop in props): - yield node - - def warning(title): - log.warning('%s, guesses: %s - %s' % (title, m.nice_string(), m2.nice_string())) + if m.get('title') is None: return m - if m.get('title') != m2.get('title'): title = next(find_nodes(mtree.match_tree, 'title')) title2 = next(find_nodes(mtree2.match_tree, 'title')) diff --git a/libs/guessit/fileutils.py b/libs/guessit/fileutils.py index 45f07e87..dc077e64 100755 --- a/libs/guessit/fileutils.py +++ b/libs/guessit/fileutils.py @@ -77,12 +77,12 @@ def file_in_same_dir(ref_file, desired_file): def load_file_in_same_dir(ref_file, filename): """Load a given file. Works even when the file is contained inside a zip.""" - path = split_path(ref_file)[:-1] + [str(filename)] + path = split_path(ref_file)[:-1] + [filename] for i, p in enumerate(path): - if p[-4:] == '.zip': + if p.endswith('.zip'): zfilename = os.path.join(*path[:i + 1]) zfile = zipfile.ZipFile(zfilename) return zfile.read('/'.join(path[i + 1:])) - return u(io.open(os.path.join(*path), encoding = 'utf-8').read()) + return u(io.open(os.path.join(*path), encoding='utf-8').read()) diff --git a/libs/guessit/guess.py b/libs/guessit/guess.py index 62385e8c..33d36517 100755 --- a/libs/guessit/guess.py +++ b/libs/guessit/guess.py @@ -295,7 +295,7 @@ def merge_all(guesses, append=None): # then merge the remaining ones dups = set(result) & set(g) if dups: - log.warning('duplicate properties %s in merged result...' % dups) + log.warning('duplicate properties %s in merged result...' % [ (result[p], g[p]) for p in dups] ) result.update_highest_confidence(g) diff --git a/libs/guessit/language.py b/libs/guessit/language.py index 3b3a86a5..2714c6e0 100755 --- a/libs/guessit/language.py +++ b/libs/guessit/language.py @@ -326,7 +326,7 @@ def search_language(string, lang_filter=None): 'la', 'el', 'del', 'por', 'mar', # other 'ind', 'arw', 'ts', 'ii', 'bin', 'chan', 'ss', 'san', 'oss', 'iii', - 'vi', 'ben', 'da' + 'vi', 'ben', 'da', 'lt' ]) sep = r'[](){} \._-+' diff --git a/libs/guessit/matcher.py b/libs/guessit/matcher.py index cc77b817..43378192 100755 --- a/libs/guessit/matcher.py +++ b/libs/guessit/matcher.py @@ -128,12 +128,14 @@ class IterativeMatcher(object): apply_transfo(name) # more guessers for both movies and episodes - for name in ['guess_bonus_features', 'guess_year']: - apply_transfo(name) + apply_transfo('guess_bonus_features') + apply_transfo('guess_year', skip_first_year=('skip_first_year' in opts)) if 'nocountry' not in opts: apply_transfo('guess_country') + apply_transfo('guess_idnumber') + # split into '-' separated subgroups (with required separator chars # around the dash) diff --git a/libs/guessit/matchtree.py b/libs/guessit/matchtree.py index 2853c3a0..0725e835 100755 --- a/libs/guessit/matchtree.py +++ b/libs/guessit/matchtree.py @@ -275,7 +275,7 @@ class MatchTree(BaseMatchTree): for string_part in ('title', 'series', 'container', 'format', 'releaseGroup', 'website', 'audioCodec', 'videoCodec', 'screenSize', 'episodeFormat', - 'audioChannels'): + 'audioChannels', 'idNumber'): merge_similar_guesses(parts, string_part, choose_string) # 2- merge the rest, potentially discarding information not properly diff --git a/libs/guessit/patterns.py b/libs/guessit/patterns.py index a8a0607c..ed3982b9 100755 --- a/libs/guessit/patterns.py +++ b/libs/guessit/patterns.py @@ -43,13 +43,13 @@ episode_rexps = [ # ... Season 2 ... (r'saison (?P[0-9]+)', 1.0, (0, 0)), # ... s02e13 ... - (r'[Ss](?P[0-9]{1,2}).?(?P(?:[Ee-][0-9]{1,2})+)[^0-9]', 1.0, (0, -1)), + (r'[Ss](?P[0-9]{1,3})[^0-9]?(?P(?:-?[eE-][0-9]{1,3})+)[^0-9]', 1.0, (0, -1)), - # ... s03-x02 ... - (r'[Ss](?P[0-9]{1,2}).?(?P(?:[Xx][0-9]{1,2})+)[^0-9]', 1.0, (0, -1)), + # ... s03-x02 ... # FIXME: redundant? remove it? + #(r'[Ss](?P[0-9]{1,3})[^0-9]?(?P(?:-?[xX-][0-9]{1,3})+)[^0-9]', 1.0, (0, -1)), # ... 2x13 ... - (r'[^0-9](?P[0-9]{1,2}).?(?P(?:[xX][0-9]{1,2})+)[^0-9]', 0.8, (1, -1)), + (r'[^0-9](?P[0-9]{1,2})[^0-9]?(?P(?:-?[xX][0-9]{1,3})+)[^0-9]', 1.0, (1, -1)), # ... s02 ... #(sep + r's(?P[0-9]{1,2})' + sep, 0.6, (1, -1)), @@ -122,20 +122,25 @@ prop_multi = { 'format': { 'DVD': [ 'DVD', 'DVD-Rip', 'VIDEO-TS', 'DVDivX' ], 'VHS': [ 'VHS' ], 'WEB-DL': [ 'WEB-DL' ] }, - 'screenSize': { '480p': [ '480p?' ], - '720p': [ '720p?' ], - '1080p': [ '1080p?' ] }, + 'screenSize': { '480p': [ '480[pi]?' ], + '720p': [ '720[pi]?' ], + '1080p': [ '1080[pi]?' ] }, 'videoCodec': { 'XviD': [ 'Xvid' ], 'DivX': [ 'DVDivX', 'DivX' ], 'h264': [ '[hx]-264' ], - 'Rv10': [ 'Rv10' ] }, + 'Rv10': [ 'Rv10' ], + 'Mpeg2': [ 'Mpeg2' ] }, + + # has nothing to do here (or on filenames for that matter), but some + # releases use it and it helps to identify release groups, so we adapt + 'videoApi': { 'DXVA': [ 'DXVA' ] }, 'audioCodec': { 'AC3': [ 'AC3' ], 'DTS': [ 'DTS' ], 'AAC': [ 'He-AAC', 'AAC-He', 'AAC' ] }, - 'audioChannels': { '5.1': [ r'5\.1', 'DD5\.1', '5ch' ] }, + 'audioChannels': { '5.1': [ r'5\.1', 'DD5[\._ ]1', '5ch' ] }, 'episodeFormat': { 'Minisode': [ 'Minisodes?' ] } @@ -143,14 +148,21 @@ prop_multi = { 'format': { 'DVD': [ 'DVD', 'DVD-Rip', 'VIDEO-TS', 'DVDivX' ], # prop_single dict of { property_name: [ canonical_form ] } prop_single = { 'releaseGroup': [ 'ESiR', 'WAF', 'SEPTiC', r'\[XCT\]', 'iNT', 'PUKKA', - 'CHD', 'ViTE', 'TLF', 'DEiTY', 'FLAiTE', - 'MDX', 'GM4F', 'DVL', 'SVD', 'iLUMiNADOS', 'FiNaLe', - 'UnSeeN', 'aXXo', 'KLAXXON', 'NoTV', 'ZeaL', 'LOL', - 'SiNNERS', 'DiRTY', 'REWARD', 'ECI', 'KiNGS', 'CLUE', - 'CtrlHD', 'POD', 'WiKi', 'DIMENSION', 'IMMERSE', 'FQM', - '2HD', 'REPTiLE', 'CTU', 'HALCYON', 'EbP', 'SiTV', - 'SAiNTS', 'HDBRiSe', 'AlFleNi-TeaM', 'EVOLVE', '0TV', - 'TLA', 'NTB', 'ASAP', 'MOMENTUM', 'FoV', 'D-Z0N3' ], + 'CHD', 'ViTE', 'TLF', 'FLAiTE', + 'MDX', 'GM4F', 'DVL', 'SVD', 'iLUMiNADOS', + 'aXXo', 'KLAXXON', 'NoTV', 'ZeaL', 'LOL', + 'CtrlHD', 'POD', 'WiKi','IMMERSE', 'FQM', + '2HD', 'CTU', 'HALCYON', 'EbP', 'SiTV', + 'HDBRiSe', 'AlFleNi-TeaM', 'EVOLVE', '0TV', + 'TLA', 'NTB', 'ASAP', 'MOMENTUM', 'FoV', 'D-Z0N3', + 'TrollHD', 'ECI' + ], + + # potentially confusing release group names (they are words) + 'weakReleaseGroup': [ 'DEiTY', 'FiNaLe', 'UnSeeN', 'KiNGS', 'CLUE', 'DIMENSION', + 'SAiNTS', 'ARROW', 'EuReKA', 'SiNNERS', 'DiRTY', 'REWARD', + 'REPTiLE', + ], 'other': [ 'PROPER', 'REPACK', 'LIMITED', 'DualAudio', 'Audiofixed', 'R5', 'complete', 'classic', # not so sure about these ones, could appear in a title @@ -179,6 +191,10 @@ properties_rexps.update(dict((type, dict((canonical_form, [ _to_rexp(canonical_f def find_properties(string): result = [] for property_name, props in properties_rexps.items(): + # FIXME: this should be done in a more flexible way... + if property_name in ['weakReleaseGroup']: + continue + for canonical_form, rexps in props.items(): for value_rexp in rexps: match = value_rexp.search(string) diff --git a/libs/guessit/transfo/guess_episodes_rexps.py b/libs/guessit/transfo/guess_episodes_rexps.py index 4ebfb547..29562be2 100755 --- a/libs/guessit/transfo/guess_episodes_rexps.py +++ b/libs/guessit/transfo/guess_episodes_rexps.py @@ -28,7 +28,13 @@ import logging log = logging.getLogger(__name__) def number_list(s): - return list(re.sub('[^0-9]+', ' ', s).split()) + l = [ int(n) for n in re.sub('[^0-9]+', ' ', s).split() ] + + if len(l) == 2: + # it is an episode interval, return all numbers in between + return range(l[0], l[1]+1) + + return l def guess_episodes_rexps(string): for rexp, confidence, span_adjust in episode_rexps: @@ -38,23 +44,23 @@ def guess_episodes_rexps(string): span = (match.start() + span_adjust[0], match.end() + span_adjust[1]) - # episodes which have a season > 25 are most likely errors + # episodes which have a season > 30 are most likely errors # (Simpsons is at 24!) - if int(guess.get('season', 0)) > 25: + if int(guess.get('season', 0)) > 30: continue # decide whether we have only a single episode number or an # episode list if guess.get('episodeNumber'): eplist = number_list(guess['episodeNumber']) - guess.set('episodeNumber', int(eplist[0]), confidence=confidence) + guess.set('episodeNumber', eplist[0], confidence=confidence) if len(eplist) > 1: - guess.set('episodeList', list(map(int, eplist)), confidence=confidence) + guess.set('episodeList', eplist, confidence=confidence) if guess.get('bonusNumber'): eplist = number_list(guess['bonusNumber']) - guess.set('bonusNumber', int(eplist[0]), confidence=confidence) + guess.set('bonusNumber', eplist[0], confidence=confidence) return guess, span diff --git a/libs/guessit/transfo/guess_idnumber.py b/libs/guessit/transfo/guess_idnumber.py new file mode 100755 index 00000000..0e15af5c --- /dev/null +++ b/libs/guessit/transfo/guess_idnumber.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# GuessIt - A library for guessing information from filenames +# Copyright (c) 2013 Nicolas Wack +# +# GuessIt is free software; you can redistribute it and/or modify it under +# the terms of the Lesser GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# GuessIt 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 +# Lesser GNU General Public License for more details. +# +# You should have received a copy of the Lesser GNU General Public License +# along with this program. If not, see . +# + +from __future__ import unicode_literals +from guessit.transfo import SingleNodeGuesser +from guessit.patterns import find_properties +import re +import logging + +log = logging.getLogger(__name__) + + +def guess_properties(string): + try: + prop, value, pos, end = find_properties(string)[0] + return { prop: value }, (pos, end) + except IndexError: + return None, None + +_idnum = re.compile(r'(?P[a-zA-Z0-9-]{10,})') # 1.0, (0, 0)) + +def guess_idnumber(string): + match = _idnum.search(string) + if match is not None: + result = match.groupdict() + switch_count = 0 + DIGIT = 0 + LETTER = 1 + OTHER = 2 + last = LETTER + for c in result['idNumber']: + if c in '0123456789': + ci = DIGIT + elif c in 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ': + ci = LETTER + else: + ci = OTHER + + if ci != last: + switch_count += 1 + + last = ci + + switch_ratio = float(switch_count) / len(result['idNumber']) + + # only return the result as probable if we alternate often between + # char type (more likely for hash values than for common words) + if switch_ratio > 0.4: + return result, match.span() + + return None, None + +def process(mtree): + SingleNodeGuesser(guess_idnumber, 0.4, log).process(mtree) diff --git a/libs/guessit/transfo/guess_release_group.py b/libs/guessit/transfo/guess_release_group.py index 2ff237d8..b72c7368 100755 --- a/libs/guessit/transfo/guess_release_group.py +++ b/libs/guessit/transfo/guess_release_group.py @@ -31,16 +31,22 @@ def get_patterns(property_name): CODECS = get_patterns('videoCodec') FORMATS = get_patterns('format') +VAPIS = get_patterns('videoApi') -GROUP_NAMES = [ r'(?P' + codec + r')-?(?P.*?)[ \.]' +# RG names following a codec or format, with a potential space or dash inside the name +GROUP_NAMES = [ r'(?P' + codec + r')[ \.-](?P.+?([- \.].*?)??)[ \.]' for codec in CODECS ] -GROUP_NAMES += [ r'(?P' + fmt + r')-?(?P.*?)[ \.]' +GROUP_NAMES += [ r'(?P' + fmt + r')[ \.-](?P.+?([- \.].*?)??)[ \.]' for fmt in FORMATS ] +GROUP_NAMES += [ r'(?P' + api + r')[ \.-](?P.+?([- \.].*?)??)[ \.]' + for api in VAPIS ] GROUP_NAMES2 = [ r'\.(?P' + codec + r')-(?P.*?)(-(.*?))?[ \.]' for codec in CODECS ] -GROUP_NAMES2 += [ r'\.(?P' + fmt + r')-(?P.*?)(-(.*?))?[ \.]' +GROUP_NAMES2 += [ r'\.(?P' + fmt + r')-(?P.*?)(-(.*?))?[ \.]' for fmt in FORMATS ] +GROUP_NAMES2 += [ r'\.(?P' + vapi + r')-(?P.*?)(-(.*?))?[ \.]' + for vapi in VAPIS ] GROUP_NAMES = [ re.compile(r, re.IGNORECASE) for r in GROUP_NAMES ] GROUP_NAMES2 = [ re.compile(r, re.IGNORECASE) for r in GROUP_NAMES2 ] @@ -54,12 +60,17 @@ def guess_release_group(string): # first try to see whether we have both a known codec and a known release group for rexp in GROUP_NAMES: match = rexp.search(string) - if match: + while match: metadata = match.groupdict() - release_group = compute_canonical_form('releaseGroup', metadata['releaseGroup']) + # make sure this is an actual release group we caught + release_group = (compute_canonical_form('releaseGroup', metadata['releaseGroup']) or + compute_canonical_form('weakReleaseGroup', metadata['releaseGroup'])) if release_group: return adjust_metadata(metadata), (match.start(1), match.end(2)) + # we didn't find anything conclusive, keep searching + match = rexp.search(string, match.span()[0]+1) + # pick anything as releaseGroup as long as we have a codec in front # this doesn't include a potential dash ('-') ending the release group # eg: [...].X264-HiS@SiLUHD-English.[...] diff --git a/libs/guessit/transfo/guess_year.py b/libs/guessit/transfo/guess_year.py index 4bc9b867..c193af7a 100755 --- a/libs/guessit/transfo/guess_year.py +++ b/libs/guessit/transfo/guess_year.py @@ -33,6 +33,18 @@ def guess_year(string): else: return None, None +def guess_year_skip_first(string): + year, span = search_year(string) + if year: + year2, span2 = guess_year(string[span[1]:]) + if year2: + return year2, (span2[0]+span[1], span2[1]+span[1]) -def process(mtree): - SingleNodeGuesser(guess_year, 1.0, log).process(mtree) + return None, None + + +def process(mtree, skip_first_year=False): + if skip_first_year: + SingleNodeGuesser(guess_year_skip_first, 1.0, log).process(mtree) + else: + SingleNodeGuesser(guess_year, 1.0, log).process(mtree) From ac30152930c1a6a7eb779381941c0228416e81ff Mon Sep 17 00:00:00 2001 From: Ruud Date: Tue, 17 Sep 2013 21:45:43 +0200 Subject: [PATCH 17/42] Don't start new long-poll right away. --- couchpotato/core/notifications/core/static/notification.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/notifications/core/static/notification.js b/couchpotato/core/notifications/core/static/notification.js index b05eb1a3..e485976e 100644 --- a/couchpotato/core/notifications/core/static/notification.js +++ b/couchpotato/core/notifications/core/static/notification.js @@ -157,7 +157,7 @@ var NotificationBase = new Class({ } // Restart poll - self.startPoll() + self.startPoll.delay(1500, self); }, showMessage: function(message, sticky, data){ From 70ba5d80cd4bc369eb50a6e384e4b23b39b9e32f Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 18 Sep 2013 21:42:25 +0200 Subject: [PATCH 18/42] Trailers not downloading. fix #1563 --- couchpotato/core/providers/trailer/hdtrailers/main.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/couchpotato/core/providers/trailer/hdtrailers/main.py b/couchpotato/core/providers/trailer/hdtrailers/main.py index 320a5835..abb91658 100644 --- a/couchpotato/core/providers/trailer/hdtrailers/main.py +++ b/couchpotato/core/providers/trailer/hdtrailers/main.py @@ -90,21 +90,18 @@ class HDTrailers(TrailerProvider): html = BeautifulSoup(data, parse_only = tables) result_table = html.find('table', attrs = {'class':'bottomTable'}) - for tr in result_table.find_all('tr'): trtext = str(tr).lower() if 'clips' in trtext: break - if 'trailer' in trtext and not 'clip' in trtext and provider in trtext: - nr = 0 + + if 'trailer' in trtext and not 'clip' in trtext and provider in trtext and not '3d' in trtext: if 'trailer' not in tr.find('span', 'standardTrailerName').text.lower(): continue resolutions = tr.find_all('td', attrs = {'class':'bottomTableResolution'}) for res in resolutions: - results[str(res.a.contents[0])].insert(0, res.a['href']) - nr += 1 - - return results + if res.a: + results[str(res.a.contents[0])].insert(0, res.a['href']) except AttributeError: log.debug('No trailers found in provider %s.', provider) From 8b45b6f1a021022d732695d39ca377e6f338d37e Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 18 Sep 2013 22:07:07 +0200 Subject: [PATCH 19/42] Only backup database max once an hour. fix #1218 --- couchpotato/runner.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/couchpotato/runner.py b/couchpotato/runner.py index c57e0770..7403a54f 100644 --- a/couchpotato/runner.py +++ b/couchpotato/runner.py @@ -84,18 +84,6 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En # Backup before start and cleanup old databases new_backup = toUnicode(os.path.join(data_dir, 'db_backup', str(int(time.time())))) - # Create path and copy - if not os.path.isdir(new_backup): os.makedirs(new_backup) - src_files = [options.config_file, db_path, db_path + '-shm', db_path + '-wal'] - for src_file in src_files: - if os.path.isfile(src_file): - dst_file = toUnicode(os.path.join(new_backup, os.path.basename(src_file))) - shutil.copyfile(src_file, dst_file) - - # Try and copy stats seperately - try: shutil.copystat(src_file, dst_file) - except: pass - # Remove older backups, keep backups 3 days or at least 3 backups = [] for directory in os.listdir(os.path.dirname(new_backup)): @@ -103,6 +91,20 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En if os.path.isdir(backup): backups.append(backup) + latest_backup = tryInt(os.path.basename(sorted(backups)[-1])) if len(backups) > 0 else 0 + if latest_backup < time.time() - 3600: + # Create path and copy + if not os.path.isdir(new_backup): os.makedirs(new_backup) + src_files = [options.config_file, db_path, db_path + '-shm', db_path + '-wal'] + for src_file in src_files: + if os.path.isfile(src_file): + dst_file = toUnicode(os.path.join(new_backup, os.path.basename(src_file))) + shutil.copyfile(src_file, dst_file) + + # Try and copy stats seperately + try: shutil.copystat(src_file, dst_file) + except: pass + total_backups = len(backups) for backup in backups: if total_backups > 3: From 6464bb065db662403fc848d8a62c696a949eda60 Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 18 Sep 2013 23:04:54 +0200 Subject: [PATCH 20/42] Better year guessing. fix #609 --- couchpotato/core/plugins/scanner/main.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/couchpotato/core/plugins/scanner/main.py b/couchpotato/core/plugins/scanner/main.py index 3221ac78..ff17b647 100644 --- a/couchpotato/core/plugins/scanner/main.py +++ b/couchpotato/core/plugins/scanner/main.py @@ -819,6 +819,13 @@ class Scanner(Plugin): return None def findYear(self, text): + + # Search year inside () or [] first + matches = re.search('(\(|\[)(?P19[0-9]{2}|20[0-9]{2})(\]|\))', text) + if matches: + return matches.group('year') + + # Search normal matches = re.search('(?P19[0-9]{2}|20[0-9]{2})', text) if matches: return matches.group('year') @@ -831,11 +838,11 @@ class Scanner(Plugin): guess = {} if file_name: try: - guess = guess_movie_info(toUnicode(file_name)) - if guess.get('title') and guess.get('year'): + guessit = guess_movie_info(toUnicode(file_name)) + if guessit.get('title') and guessit.get('year'): guess = { - 'name': guess.get('title'), - 'year': guess.get('year'), + 'name': guessit.get('title'), + 'year': guessit.get('year'), } except: log.debug('Could not detect via guessit "%s": %s', (file_name, traceback.format_exc())) @@ -843,7 +850,13 @@ class Scanner(Plugin): # Backup to simple cleaned = ' '.join(re.split('\W+', simplifyString(release_name))) cleaned = re.sub(self.clean, ' ', cleaned) - year = self.findYear(cleaned) + + for year_str in [file_name, cleaned]: + if not year_str: continue + year = self.findYear(year_str) + if year: + break + cp_guess = {} if year: # Split name on year From 96291f63da15ed571bb8385a25ba325eae443be2 Mon Sep 17 00:00:00 2001 From: Ruud Date: Thu, 19 Sep 2013 22:11:10 +0200 Subject: [PATCH 21/42] Create db backup dir before trying to use it. fix #2207 --- couchpotato/runner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/runner.py b/couchpotato/runner.py index 7403a54f..571023ea 100644 --- a/couchpotato/runner.py +++ b/couchpotato/runner.py @@ -83,6 +83,7 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En # Backup before start and cleanup old databases new_backup = toUnicode(os.path.join(data_dir, 'db_backup', str(int(time.time())))) + if not os.path.isdir(new_backup): os.makedirs(new_backup) # Remove older backups, keep backups 3 days or at least 3 backups = [] @@ -94,7 +95,6 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En latest_backup = tryInt(os.path.basename(sorted(backups)[-1])) if len(backups) > 0 else 0 if latest_backup < time.time() - 3600: # Create path and copy - if not os.path.isdir(new_backup): os.makedirs(new_backup) src_files = [options.config_file, db_path, db_path + '-shm', db_path + '-wal'] for src_file in src_files: if os.path.isfile(src_file): From 33d7d994d41c9ec1870771bd0a22f8e1d2360218 Mon Sep 17 00:00:00 2001 From: Ruud Date: Thu, 19 Sep 2013 23:16:49 +0200 Subject: [PATCH 22/42] Don't try to finish an already closed connection --- couchpotato/api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/couchpotato/api.py b/couchpotato/api.py index a9f449be..b6135583 100644 --- a/couchpotato/api.py +++ b/couchpotato/api.py @@ -50,7 +50,8 @@ class NonBlockHandler(RequestHandler): self.finish(response) except: log.error('Failed doing nonblock request: %s', (traceback.format_exc())) - self.finish({'success': False, 'error': 'Failed returning results'}) + try: self.finish({'success': False, 'error': 'Failed returning results'}) + except: pass def on_connection_close(self): From c75ac51eb7b02e85a82f63e69b9d4f2962dcbc3b Mon Sep 17 00:00:00 2001 From: Ruud Date: Thu, 19 Sep 2013 23:29:21 +0200 Subject: [PATCH 23/42] Try the info dict to get title. fix #2206 --- couchpotato/core/helpers/variable.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/couchpotato/core/helpers/variable.py b/couchpotato/core/helpers/variable.py index fd1a101d..2b288740 100644 --- a/couchpotato/core/helpers/variable.py +++ b/couchpotato/core/helpers/variable.py @@ -170,8 +170,11 @@ def getTitle(library_dict): if title.default: return title.title except: - log.error('Could not get title for %s', library_dict.identifier) - return None + try: + return library_dict['info']['titles'][0] + except: + log.error('Could not get title for %s', library_dict.identifier) + return None log.error('Could not get title for %s', library_dict['identifier']) return None From c8ab6a06fb6a8117927b6c4a7235a0caa417f1f8 Mon Sep 17 00:00:00 2001 From: Ruud Date: Thu, 19 Sep 2013 23:39:15 +0200 Subject: [PATCH 24/42] ASCII encode md5 string. closes #2167 --- couchpotato/core/_base/_core/main.py | 2 +- couchpotato/core/helpers/variable.py | 2 +- couchpotato/core/media/movie/searcher/main.py | 2 +- couchpotato/core/plugins/base.py | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/couchpotato/core/_base/_core/main.py b/couchpotato/core/_base/_core/main.py index 9647d959..803ac5a3 100644 --- a/couchpotato/core/_base/_core/main.py +++ b/couchpotato/core/_base/_core/main.py @@ -56,7 +56,7 @@ class Core(Plugin): self.signalHandler() def md5Password(self, value): - return md5(value.encode(Env.get('encoding'))) if value else '' + return md5(value) if value else '' def checkApikey(self, value): return value if value and len(value) > 3 else uuid4().hex diff --git a/couchpotato/core/helpers/variable.py b/couchpotato/core/helpers/variable.py index 2b288740..1e7fc837 100644 --- a/couchpotato/core/helpers/variable.py +++ b/couchpotato/core/helpers/variable.py @@ -101,7 +101,7 @@ def flattenList(l): return l def md5(text): - return hashlib.md5(text).hexdigest() + return hashlib.md5(ss(text)).hexdigest() def sha1(text): return hashlib.sha1(text).hexdigest() diff --git a/couchpotato/core/media/movie/searcher/main.py b/couchpotato/core/media/movie/searcher/main.py index a8fca185..b08e7532 100644 --- a/couchpotato/core/media/movie/searcher/main.py +++ b/couchpotato/core/media/movie/searcher/main.py @@ -1,7 +1,7 @@ from couchpotato import get_session from couchpotato.api import addApiView from couchpotato.core.event import addEvent, fireEvent, fireEventAsync -from couchpotato.core.helpers.encoding import simplifyString, toUnicode +from couchpotato.core.helpers.encoding import simplifyString, toUnicode, ss from couchpotato.core.helpers.variable import md5, getTitle, splitString, \ possibleTitles, getImdb from couchpotato.core.logger import CPLog diff --git a/couchpotato/core/plugins/base.py b/couchpotato/core/plugins/base.py index b9ec0c06..ce7c1b49 100644 --- a/couchpotato/core/plugins/base.py +++ b/couchpotato/core/plugins/base.py @@ -259,7 +259,7 @@ class Plugin(object): def getCache(self, cache_key, url = None, **kwargs): - cache_key_md5 = md5(ss(cache_key)) + cache_key_md5 = md5(cache_key) cache = Env.get('cache').get(cache_key_md5) if cache: if not Env.get('dev'): log.debug('Getting cache %s', cache_key) @@ -284,7 +284,7 @@ class Plugin(object): return '' def setCache(self, cache_key, value, timeout = 300): - cache_key_md5 = md5(ss(cache_key)) + cache_key_md5 = md5(cache_key) log.debug('Setting cache %s', cache_key) Env.get('cache').set(cache_key_md5, value, timeout) return value From b0781b45f830d4961178129a36de065ff4409c04 Mon Sep 17 00:00:00 2001 From: Ruud Date: Thu, 19 Sep 2013 23:49:23 +0200 Subject: [PATCH 25/42] Different seperator for folder and filename --- couchpotato/core/plugins/renamer/__init__.py | 8 +++++++- couchpotato/core/plugins/renamer/main.py | 8 ++++---- 2 files changed, 11 insertions(+), 5 deletions(-) mode change 100644 => 100755 couchpotato/core/plugins/renamer/main.py diff --git a/couchpotato/core/plugins/renamer/__init__.py b/couchpotato/core/plugins/renamer/__init__.py index 6472a2df..921b3e1e 100755 --- a/couchpotato/core/plugins/renamer/__init__.py +++ b/couchpotato/core/plugins/renamer/__init__.py @@ -120,7 +120,13 @@ config = [{ { 'advanced': True, 'name': 'separator', - 'label': 'Separator', + 'label': 'File-Separator', + 'description': 'Replace all the spaces with a character. Example: ".", "-" (without quotes). Leave empty to use spaces.', + }, + { + 'advanced': True, + 'name': 'foldersep', + 'label': 'Folder-Separator', 'description': 'Replace all the spaces with a character. Example: ".", "-" (without quotes). Leave empty to use spaces.', }, { diff --git a/couchpotato/core/plugins/renamer/main.py b/couchpotato/core/plugins/renamer/main.py old mode 100644 new mode 100755 index 0013298a..ad7df1cf --- a/couchpotato/core/plugins/renamer/main.py +++ b/couchpotato/core/plugins/renamer/main.py @@ -252,7 +252,7 @@ class Renamer(Plugin): replacements['cd_nr'] = cd if multiple else '' # Naming - final_folder_name = self.doReplace(folder_name, replacements) + final_folder_name = self.doReplace(folder_name, replacements, folder = True) final_file_name = self.doReplace(file_name, replacements) replacements['filename'] = final_file_name[:-(len(getExt(final_file_name)) + 1)] @@ -508,7 +508,7 @@ class Renamer(Plugin): for extra in set(filter(test, group['files'][extra_type])): replacements['ext'] = getExt(extra) - final_folder_name = self.doReplace(folder_name, replacements, remove_multiple = remove_multiple) + final_folder_name = self.doReplace(folder_name, replacements, remove_multiple = remove_multiple, folder = True) final_file_name = self.doReplace(file_name, replacements, remove_multiple = remove_multiple) rename_files[extra] = os.path.join(destination, final_folder_name, final_file_name) @@ -603,7 +603,7 @@ Remove it if you want it to be renamed (again, or at least let it try again) return True - def doReplace(self, string, replacements, remove_multiple = False): + def doReplace(self, string, replacements, remove_multiple = False, folder = False): """ replace confignames with the real thing """ @@ -623,7 +623,7 @@ Remove it if you want it to be renamed (again, or at least let it try again) replaced = re.sub(r"[\x00:\*\?\"<>\|]", '', replaced) - sep = self.conf('separator') + sep = self.conf('foldersep') if folder else self.conf('separator') return self.replaceDoubles(replaced.lstrip('. ')).replace(' ', ' ' if not sep else sep) def replaceDoubles(self, string): From 7c79c6d1f3713a55bb44317e2d4dfaddb51581ae Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 20 Sep 2013 12:51:58 +0200 Subject: [PATCH 26/42] Update TorrentShack url. fix #2209 --- .../core/providers/torrent/torrentshack/main.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/couchpotato/core/providers/torrent/torrentshack/main.py b/couchpotato/core/providers/torrent/torrentshack/main.py index b9d12c71..353b606e 100644 --- a/couchpotato/core/providers/torrent/torrentshack/main.py +++ b/couchpotato/core/providers/torrent/torrentshack/main.py @@ -11,12 +11,12 @@ log = CPLog(__name__) class TorrentShack(TorrentProvider): urls = { - 'test' : 'http://www.torrentshack.net/', - 'login' : 'http://www.torrentshack.net/login.php', - 'login_check': 'http://www.torrentshack.net/inbox.php', - 'detail' : 'http://www.torrentshack.net/torrent/%s', - 'search' : 'http://www.torrentshack.net/torrents.php?searchstr=%s&filter_cat[%d]=1', - 'download' : 'http://www.torrentshack.net/%s', + 'test' : 'https://torrentshack.net/', + 'login' : 'https://torrentshack.net/login.php', + 'login_check': 'https://torrentshack.net/inbox.php', + 'detail' : 'https://torrentshack.net/torrent/%s', + 'search' : 'https://torrentshack.net/torrents.php?searchstr=%s&filter_cat[%d]=1', + 'download' : 'https://torrentshack.net/%s', } cat_ids = [ From 8d38fa87a4971d0a9ae2a9199da9bd0e74fc7731 Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 20 Sep 2013 16:06:23 +0200 Subject: [PATCH 27/42] Copy unrar dll to cache folder. fix #2205 --- libs/unrar2/PKG-INFO | 27 - libs/unrar2/UnRAR2.html | 191 ------- libs/unrar2/UnRARDLL/license.txt | 18 - libs/unrar2/UnRARDLL/unrar.h | 140 ----- libs/unrar2/UnRARDLL/unrar.lib | Bin 4114 -> 0 bytes libs/unrar2/UnRARDLL/unrardll.txt | 606 --------------------- libs/unrar2/UnRARDLL/whatsnew.txt | 80 --- libs/unrar2/UnRARDLL/x64/readme.txt | 1 - libs/unrar2/UnRARDLL/x64/unrar64.lib | Bin 3972 -> 0 bytes libs/unrar2/license.txt | 21 - libs/unrar2/unix.py | 66 +-- libs/unrar2/unrar | Bin 0 -> 234984 bytes libs/unrar2/{UnRARDLL => }/unrar.dll | Bin libs/unrar2/{UnRARDLL/x64 => }/unrar64.dll | Bin libs/unrar2/windows.py | 27 +- 15 files changed, 49 insertions(+), 1128 deletions(-) delete mode 100644 libs/unrar2/PKG-INFO delete mode 100644 libs/unrar2/UnRAR2.html delete mode 100644 libs/unrar2/UnRARDLL/license.txt delete mode 100644 libs/unrar2/UnRARDLL/unrar.h delete mode 100644 libs/unrar2/UnRARDLL/unrar.lib delete mode 100644 libs/unrar2/UnRARDLL/unrardll.txt delete mode 100644 libs/unrar2/UnRARDLL/whatsnew.txt delete mode 100644 libs/unrar2/UnRARDLL/x64/readme.txt delete mode 100644 libs/unrar2/UnRARDLL/x64/unrar64.lib delete mode 100644 libs/unrar2/license.txt create mode 100755 libs/unrar2/unrar rename libs/unrar2/{UnRARDLL => }/unrar.dll (100%) rename libs/unrar2/{UnRARDLL/x64 => }/unrar64.dll (100%) diff --git a/libs/unrar2/PKG-INFO b/libs/unrar2/PKG-INFO deleted file mode 100644 index 7e495929..00000000 --- a/libs/unrar2/PKG-INFO +++ /dev/null @@ -1,27 +0,0 @@ -Metadata-Version: 1.0 -Name: pyUnRAR2 -Version: 0.99.2 -Summary: Improved Python wrapper around the free UnRAR.dll -Home-page: http://code.google.com/py-unrar2 -Author: Konstantin Yegupov -Author-email: yk4ever@gmail.com -License: MIT -Description: pyUnRAR2 is a ctypes based wrapper around the free UnRAR.dll. - - It is an modified version of Jimmy Retzlaff's pyUnRAR - more simple, - stable and foolproof. - Notice that it has INCOMPATIBLE interface. - - It enables reading and unpacking of archives created with the - RAR/WinRAR archivers. There is a low-level interface which is very - similar to the C interface provided by UnRAR. There is also a - higher level interface which makes some common operations easier. -Platform: Windows -Classifier: Development Status :: 4 - Beta -Classifier: Environment :: Win32 (MS Windows) -Classifier: License :: OSI Approved :: MIT License -Classifier: Natural Language :: English -Classifier: Operating System :: Microsoft :: Windows -Classifier: Programming Language :: Python -Classifier: Topic :: Software Development :: Libraries :: Python Modules -Classifier: Topic :: System :: Archiving :: Compression diff --git a/libs/unrar2/UnRAR2.html b/libs/unrar2/UnRAR2.html deleted file mode 100644 index 0553ee04..00000000 --- a/libs/unrar2/UnRAR2.html +++ /dev/null @@ -1,191 +0,0 @@ - - -Python: package UnRAR2 - - - - -
 
- 
UnRAR2 (version 0.99.1)
index
c:\python26\lib\site-packages\unrar2\__init__.py
-

pyUnRAR2 is a ctypes based wrapper around the free UnRAR.dll. 

-It is an modified version of Jimmy Retzlaff's pyUnRAR - more simple,
-stable and foolproof.
-Notice that it has INCOMPATIBLE interface.

-It enables reading and unpacking of archives created with the
-RAR/WinRAR archivers. There is a low-level interface which is very
-similar to the C interface provided by UnRAR. There is also a
-higher level interface which makes some common operations easier.

-

- - - - - -
 
-Package Contents
       
rar_exceptions
-setup
-
test_UnRAR2
-unix
-
windows
-

- - - - - -
 
-Classes
       
-
UnRAR2.windows.RarFileImplementation(__builtin__.object) -
-
-
RarFile -
-
-
__builtin__.object -
-
-
RarInfo -
-
-
-

- - - - - -
 
-class RarFile(UnRAR2.windows.RarFileImplementation)
    
Method resolution order:
-
RarFile
-
UnRAR2.windows.RarFileImplementation
-
__builtin__.object
-
-
-Methods defined here:
-
__del__(self)
- -
__init__(self, archiveName, password=None)
Instantiate the archive.

-archiveName is the name of the RAR file.
-password is used to decrypt the files in the archive.

-Properties:
-    comment - comment associated with the archive

->>> print RarFile('test.rar').comment
-This is a test.
- -
extract(self, condition='*', path='.', withSubpath=True, overwrite=True)
Extract specific files from archive to disk.

-If "condition" is a list of numbers, then extract files which have those positions in infolist.
-If "condition" is a string, then it is treated as a wildcard for names of files to extract.
-If "condition" is a function, it is treated as a callback function, which accepts a RarInfo object
-    and returns either boolean True (extract) or boolean False (skip).
-DEPRECATED: If "condition" callback returns string (only supported for Windows) - 
-    that string will be used as a new name to save the file under.
-If "condition" is omitted, all files are extracted.

-"path" is a directory to extract to
-"withSubpath" flag denotes whether files are extracted with their full path in the archive.
-"overwrite" flag denotes whether extracted files will overwrite old ones. Defaults to true.

-Returns list of RarInfos for extracted files.
- -
infoiter(self)
Iterate over all the files in the archive, generating RarInfos.

->>> import os
->>> for fileInArchive in RarFile('test.rar').infoiter():
-...     print os.path.split(fileInArchive.filename)[-1],
-...     print fileInArchive.isdir,
-...     print fileInArchive.size,
-...     print fileInArchive.comment,
-...     print tuple(fileInArchive.datetime)[0:5],
-...     print time.strftime('%a, %d %b %Y %H:%M', fileInArchive.datetime)
-test True 0 None (2003, 6, 30, 1, 59) Mon, 30 Jun 2003 01:59
-test.txt False 20 None (2003, 6, 30, 2, 1) Mon, 30 Jun 2003 02:01
-this.py False 1030 None (2002, 2, 8, 16, 47) Fri, 08 Feb 2002 16:47
- -
infolist(self)
Return a list of RarInfos, descripting the contents of the archive.
- -
read_files(self, condition='*')
Read specific files from archive into memory.
-If "condition" is a list of numbers, then return files which have those positions in infolist.
-If "condition" is a string, then it is treated as a wildcard for names of files to extract.
-If "condition" is a function, it is treated as a callback function, which accepts a RarInfo object 
-    and returns boolean True (extract) or False (skip).
-If "condition" is omitted, all files are returned.

-Returns list of tuples (RarInfo info, str contents)
- -
-Methods inherited from UnRAR2.windows.RarFileImplementation:
-
destruct(self)
- -
init(self, password=None)
- -
make_sure_ready(self)
- -
-Data descriptors inherited from UnRAR2.windows.RarFileImplementation:
-
__dict__
-
dictionary for instance variables (if defined)
-
-
__weakref__
-
list of weak references to the object (if defined)
-
-

- - - - - - - -
 
-class RarInfo(__builtin__.object)
   Represents a file header in an archive. Don't instantiate directly.
-Use only to obtain information about file.
-YOU CANNOT EXTRACT FILE CONTENTS USING THIS OBJECT.
-USE METHODS OF RarFile CLASS INSTEAD.

-Properties:
-    index - index of file within the archive
-    filename - name of the file in the archive including path (if any)
-    datetime - file date/time as a struct_time suitable for time.strftime
-    isdir - True if the file is a directory
-    size - size in bytes of the uncompressed file
-    comment - comment associated with the file
-    
-Note - this is not currently intended to be a Python file-like object.
 
 Methods defined here:
-
__init__(self, rarfile, data)
- -
__str__(self)
- -
-Data descriptors defined here:
-
__dict__
-
dictionary for instance variables (if defined)
-
-
__weakref__
-
list of weak references to the object (if defined)
-
-

- - - - - -
 
-Functions
       
condition2checker(condition)
Converts different condition types to callback
-

- - - - - -
 
-Data
       __version__ = '0.99.1'
-in_windows = True
- \ No newline at end of file diff --git a/libs/unrar2/UnRARDLL/license.txt b/libs/unrar2/UnRARDLL/license.txt deleted file mode 100644 index 0c1540e5..00000000 --- a/libs/unrar2/UnRARDLL/license.txt +++ /dev/null @@ -1,18 +0,0 @@ - The unrar.dll library is freeware. This means: - - 1. All copyrights to RAR and the unrar.dll are exclusively - owned by the author - Alexander Roshal. - - 2. The unrar.dll library may be used in any software to handle RAR - archives without limitations free of charge. - - 3. THE RAR ARCHIVER AND THE UNRAR.DLL LIBRARY ARE DISTRIBUTED "AS IS". - NO WARRANTY OF ANY KIND IS EXPRESSED OR IMPLIED. YOU USE AT - YOUR OWN RISK. THE AUTHOR WILL NOT BE LIABLE FOR DATA LOSS, - DAMAGES, LOSS OF PROFITS OR ANY OTHER KIND OF LOSS WHILE USING - OR MISUSING THIS SOFTWARE. - - Thank you for your interest in RAR and unrar.dll. - - - Alexander L. Roshal \ No newline at end of file diff --git a/libs/unrar2/UnRARDLL/unrar.h b/libs/unrar2/UnRARDLL/unrar.h deleted file mode 100644 index 4582f2c6..00000000 --- a/libs/unrar2/UnRARDLL/unrar.h +++ /dev/null @@ -1,140 +0,0 @@ -#ifndef _UNRAR_DLL_ -#define _UNRAR_DLL_ - -#define ERAR_END_ARCHIVE 10 -#define ERAR_NO_MEMORY 11 -#define ERAR_BAD_DATA 12 -#define ERAR_BAD_ARCHIVE 13 -#define ERAR_UNKNOWN_FORMAT 14 -#define ERAR_EOPEN 15 -#define ERAR_ECREATE 16 -#define ERAR_ECLOSE 17 -#define ERAR_EREAD 18 -#define ERAR_EWRITE 19 -#define ERAR_SMALL_BUF 20 -#define ERAR_UNKNOWN 21 -#define ERAR_MISSING_PASSWORD 22 - -#define RAR_OM_LIST 0 -#define RAR_OM_EXTRACT 1 -#define RAR_OM_LIST_INCSPLIT 2 - -#define RAR_SKIP 0 -#define RAR_TEST 1 -#define RAR_EXTRACT 2 - -#define RAR_VOL_ASK 0 -#define RAR_VOL_NOTIFY 1 - -#define RAR_DLL_VERSION 4 - -#ifdef _UNIX -#define CALLBACK -#define PASCAL -#define LONG long -#define HANDLE void * -#define LPARAM long -#define UINT unsigned int -#endif - -struct RARHeaderData -{ - char ArcName[260]; - char FileName[260]; - unsigned int Flags; - unsigned int PackSize; - unsigned int UnpSize; - unsigned int HostOS; - unsigned int FileCRC; - unsigned int FileTime; - unsigned int UnpVer; - unsigned int Method; - unsigned int FileAttr; - char *CmtBuf; - unsigned int CmtBufSize; - unsigned int CmtSize; - unsigned int CmtState; -}; - - -struct RARHeaderDataEx -{ - char ArcName[1024]; - wchar_t ArcNameW[1024]; - char FileName[1024]; - wchar_t FileNameW[1024]; - unsigned int Flags; - unsigned int PackSize; - unsigned int PackSizeHigh; - unsigned int UnpSize; - unsigned int UnpSizeHigh; - unsigned int HostOS; - unsigned int FileCRC; - unsigned int FileTime; - unsigned int UnpVer; - unsigned int Method; - unsigned int FileAttr; - char *CmtBuf; - unsigned int CmtBufSize; - unsigned int CmtSize; - unsigned int CmtState; - unsigned int Reserved[1024]; -}; - - -struct RAROpenArchiveData -{ - char *ArcName; - unsigned int OpenMode; - unsigned int OpenResult; - char *CmtBuf; - unsigned int CmtBufSize; - unsigned int CmtSize; - unsigned int CmtState; -}; - -struct RAROpenArchiveDataEx -{ - char *ArcName; - wchar_t *ArcNameW; - unsigned int OpenMode; - unsigned int OpenResult; - char *CmtBuf; - unsigned int CmtBufSize; - unsigned int CmtSize; - unsigned int CmtState; - unsigned int Flags; - unsigned int Reserved[32]; -}; - -enum UNRARCALLBACK_MESSAGES { - UCM_CHANGEVOLUME,UCM_PROCESSDATA,UCM_NEEDPASSWORD -}; - -typedef int (CALLBACK *UNRARCALLBACK)(UINT msg,LPARAM UserData,LPARAM P1,LPARAM P2); - -typedef int (PASCAL *CHANGEVOLPROC)(char *ArcName,int Mode); -typedef int (PASCAL *PROCESSDATAPROC)(unsigned char *Addr,int Size); - -#ifdef __cplusplus -extern "C" { -#endif - -HANDLE PASCAL RAROpenArchive(struct RAROpenArchiveData *ArchiveData); -HANDLE PASCAL RAROpenArchiveEx(struct RAROpenArchiveDataEx *ArchiveData); -int PASCAL RARCloseArchive(HANDLE hArcData); -int PASCAL RARReadHeader(HANDLE hArcData,struct RARHeaderData *HeaderData); -int PASCAL RARReadHeaderEx(HANDLE hArcData,struct RARHeaderDataEx *HeaderData); -int PASCAL RARProcessFile(HANDLE hArcData,int Operation,char *DestPath,char *DestName); -int PASCAL RARProcessFileW(HANDLE hArcData,int Operation,wchar_t *DestPath,wchar_t *DestName); -void PASCAL RARSetCallback(HANDLE hArcData,UNRARCALLBACK Callback,LPARAM UserData); -void PASCAL RARSetChangeVolProc(HANDLE hArcData,CHANGEVOLPROC ChangeVolProc); -void PASCAL RARSetProcessDataProc(HANDLE hArcData,PROCESSDATAPROC ProcessDataProc); -void PASCAL RARSetPassword(HANDLE hArcData,char *Password); -int PASCAL RARGetDllVersion(); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/libs/unrar2/UnRARDLL/unrar.lib b/libs/unrar2/UnRARDLL/unrar.lib deleted file mode 100644 index 0f6b3146b8ec5bd83698122653a75bcb1f2caf70..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4114 zcmcInOK%fN5dLhFhn=T@+wLMGkQOc`amG%NR?FnI5XB_X#DU{5*aNW`;>Zr*8-HML zd*Q%x;YaK>;<|@D?B2N`{sm&IW_o&h+GA%dLQ6H>T~*y*cXd^F&DCF=PUG;`!mVPw zES9S))g_~PdnwLe5Z&davS>Xj0QdnIUjrtaK>iId^&z0?62MgW9MDV;@aYrPMAL5r znxQ$EW-URdR1?j;6I7;}XsXU++gtbdcCEU-vAMr)ZSB=}E&Ih$$LYYfcMfW`elcGA z@<3X@cd)Z-i=VOy)#?y-BcN;YV{bWMY1Xgxo+6Zmn>&E6p0KtkH%w4+SmTCs;v|hq5Q}k6xBIHyY3eY03ZFFZ zx+fc+_rUFRTkRurLD_;X896SDC@$tGFxJL_<|ObY4}6#cO4Gn+^7P&e@QLUx^$S#6 zv%o3QI~r6bs*^4S6~-}#3m8Kl1x#QPQ<%mqW{^R4pe*P6b%LAen@j2DWH4cH=}d8! z^o%ndHaMl2_XySiKZu_jIZVRQ_s9EL*FhBJx|L-3_t{EHQd}6?^`Ki%PNfL6xQkm- z4v5&=1)ztX9FY`bs!)xL7(ci^ln5Mfhx#{bsp)wlRL*)ijFpOfIck|4Hvju`dm=+` z2YEY{OsVNUe)07Be$WN(P~-QoBWe@#Yo%6`ZinmiDg@;+ReuwG6#X34CKgVGURAIu zj({&jp&s*16i>5M&r_Un$;(asj7#$q#NpYvusq+pc)!)?w7cymC&e4q&0=k9XWN%* zABt^%AWr}aV^9ds(|62oNeq~c_VZ&}XTJ9bzJ3kCSf2|oEQ@fvCfyUvISe`e#&~(T zkYlh8F(REx#9{sw{)obJ0n4JtRTew+J--3gkftw+ zK8lAdg>=Obk^Q|ACe<1m zhiOKj>CaIFCtE3_O9q#Q_9LNX1zP-xlL(Nllvu-dmg~pzpG}D|GMX{u)Gi1#<;CSh zHv&7?Qyc3?^WXOfPPS57(pXOR$RI}yJTgiSDE*ZHqx<79J5Gq5MOc0!@}1Bo1)7%K zd;?lV{?EoE`zm>VUP05+(QiN;7H@?JQOUz1Fxg7!C6zF>(qj7>?T-H(IN?vsp(PLs F{{t7Z#m)c# diff --git a/libs/unrar2/UnRARDLL/unrardll.txt b/libs/unrar2/UnRARDLL/unrardll.txt deleted file mode 100644 index c49dd5b4..00000000 --- a/libs/unrar2/UnRARDLL/unrardll.txt +++ /dev/null @@ -1,606 +0,0 @@ - - UnRAR.dll Manual - ~~~~~~~~~~~~~~~~ - - UnRAR.dll is a 32-bit Windows dynamic-link library which provides - file extraction from RAR archives. - - - Exported functions - -==================================================================== -HANDLE PASCAL RAROpenArchive(struct RAROpenArchiveData *ArchiveData) -==================================================================== - -Description -~~~~~~~~~~~ - Open RAR archive and allocate memory structures - -Parameters -~~~~~~~~~~ -ArchiveData Points to RAROpenArchiveData structure - -struct RAROpenArchiveData -{ - char *ArcName; - UINT OpenMode; - UINT OpenResult; - char *CmtBuf; - UINT CmtBufSize; - UINT CmtSize; - UINT CmtState; -}; - -Structure fields: - -ArcName - Input parameter which should point to zero terminated string - containing the archive name. - -OpenMode - Input parameter. - - Possible values - - RAR_OM_LIST - Open archive for reading file headers only. - - RAR_OM_EXTRACT - Open archive for testing and extracting files. - - RAR_OM_LIST_INCSPLIT - Open archive for reading file headers only. If you open an archive - in such mode, RARReadHeader[Ex] will return all file headers, - including those with "file continued from previous volume" flag. - In case of RAR_OM_LIST such headers are automatically skipped. - So if you process RAR volumes in RAR_OM_LIST_INCSPLIT mode, you will - get several file header records for same file if file is split between - volumes. For such files only the last file header record will contain - the correct file CRC and if you wish to get the correct packed size, - you need to sum up packed sizes of all parts. - -OpenResult - Output parameter. - - Possible values - - 0 Success - ERAR_NO_MEMORY Not enough memory to initialize data structures - ERAR_BAD_DATA Archive header broken - ERAR_BAD_ARCHIVE File is not valid RAR archive - ERAR_UNKNOWN_FORMAT Unknown encryption used for archive headers - ERAR_EOPEN File open error - -CmtBuf - Input parameter which should point to the buffer for archive - comments. Maximum comment size is limited to 64Kb. Comment text is - zero terminated. If the comment text is larger than the buffer - size, the comment text will be truncated. If CmtBuf is set to - NULL, comments will not be read. - -CmtBufSize - Input parameter which should contain size of buffer for archive - comments. - -CmtSize - Output parameter containing size of comments actually read into the - buffer, cannot exceed CmtBufSize. - -CmtState - Output parameter. - - Possible values - - 0 comments not present - 1 Comments read completely - ERAR_NO_MEMORY Not enough memory to extract comments - ERAR_BAD_DATA Broken comment - ERAR_UNKNOWN_FORMAT Unknown comment format - ERAR_SMALL_BUF Buffer too small, comments not completely read - -Return values -~~~~~~~~~~~~~ - Archive handle or NULL in case of error - - -======================================================================== -HANDLE PASCAL RAROpenArchiveEx(struct RAROpenArchiveDataEx *ArchiveData) -======================================================================== - -Description -~~~~~~~~~~~ - Similar to RAROpenArchive, but uses RAROpenArchiveDataEx structure - allowing to specify Unicode archive name and returning information - about archive flags. - -Parameters -~~~~~~~~~~ -ArchiveData Points to RAROpenArchiveDataEx structure - -struct RAROpenArchiveDataEx -{ - char *ArcName; - wchar_t *ArcNameW; - unsigned int OpenMode; - unsigned int OpenResult; - char *CmtBuf; - unsigned int CmtBufSize; - unsigned int CmtSize; - unsigned int CmtState; - unsigned int Flags; - unsigned int Reserved[32]; -}; - -Structure fields: - -ArcNameW - Input parameter which should point to zero terminated Unicode string - containing the archive name or NULL if Unicode name is not specified. - -Flags - Output parameter. Combination of bit flags. - - Possible values - - 0x0001 - Volume attribute (archive volume) - 0x0002 - Archive comment present - 0x0004 - Archive lock attribute - 0x0008 - Solid attribute (solid archive) - 0x0010 - New volume naming scheme ('volname.partN.rar') - 0x0020 - Authenticity information present - 0x0040 - Recovery record present - 0x0080 - Block headers are encrypted - 0x0100 - First volume (set only by RAR 3.0 and later) - -Reserved[32] - Reserved for future use. Must be zero. - -Information on other structure fields and function return values -is available above, in RAROpenArchive function description. - - -==================================================================== -int PASCAL RARCloseArchive(HANDLE hArcData) -==================================================================== - -Description -~~~~~~~~~~~ - Close RAR archive and release allocated memory. It must be called when - archive processing is finished, even if the archive processing was stopped - due to an error. - -Parameters -~~~~~~~~~~ -hArcData - This parameter should contain the archive handle obtained from the - RAROpenArchive function call. - -Return values -~~~~~~~~~~~~~ - 0 Success - ERAR_ECLOSE Archive close error - - -==================================================================== -int PASCAL RARReadHeader(HANDLE hArcData, - struct RARHeaderData *HeaderData) -==================================================================== - -Description -~~~~~~~~~~~ - Read header of file in archive. - -Parameters -~~~~~~~~~~ -hArcData - This parameter should contain the archive handle obtained from the - RAROpenArchive function call. - -HeaderData - It should point to RARHeaderData structure: - -struct RARHeaderData -{ - char ArcName[260]; - char FileName[260]; - UINT Flags; - UINT PackSize; - UINT UnpSize; - UINT HostOS; - UINT FileCRC; - UINT FileTime; - UINT UnpVer; - UINT Method; - UINT FileAttr; - char *CmtBuf; - UINT CmtBufSize; - UINT CmtSize; - UINT CmtState; -}; - -Structure fields: - -ArcName - Output parameter which contains a zero terminated string of the - current archive name. May be used to determine the current volume - name. - -FileName - Output parameter which contains a zero terminated string of the - file name in OEM (DOS) encoding. - -Flags - Output parameter which contains file flags: - - 0x01 - file continued from previous volume - 0x02 - file continued on next volume - 0x04 - file encrypted with password - 0x08 - file comment present - 0x10 - compression of previous files is used (solid flag) - - bits 7 6 5 - - 0 0 0 - dictionary size 64 Kb - 0 0 1 - dictionary size 128 Kb - 0 1 0 - dictionary size 256 Kb - 0 1 1 - dictionary size 512 Kb - 1 0 0 - dictionary size 1024 Kb - 1 0 1 - dictionary size 2048 KB - 1 1 0 - dictionary size 4096 KB - 1 1 1 - file is directory - - Other bits are reserved. - -PackSize - Output parameter means packed file size or size of the - file part if file was split between volumes. - -UnpSize - Output parameter - unpacked file size. - -HostOS - Output parameter - operating system used for archiving: - - 0 - MS DOS; - 1 - OS/2. - 2 - Win32 - 3 - Unix - -FileCRC - Output parameter which contains unpacked file CRC. In case of file parts - split between volumes only the last part contains the correct CRC - and it is accessible only in RAR_OM_LIST_INCSPLIT listing mode. - -FileTime - Output parameter - contains date and time in standard MS DOS format. - -UnpVer - Output parameter - RAR version needed to extract file. - It is encoded as 10 * Major version + minor version. - -Method - Output parameter - packing method. - -FileAttr - Output parameter - file attributes. - -CmtBuf - File comments support is not implemented in the new DLL version yet. - Now CmtState is always 0. - -/* - * Input parameter which should point to the buffer for file - * comments. Maximum comment size is limited to 64Kb. Comment text is - * a zero terminated string in OEM encoding. If the comment text is - * larger than the buffer size, the comment text will be truncated. - * If CmtBuf is set to NULL, comments will not be read. - */ - -CmtBufSize - Input parameter which should contain size of buffer for archive - comments. - -CmtSize - Output parameter containing size of comments actually read into the - buffer, should not exceed CmtBufSize. - -CmtState - Output parameter. - - Possible values - - 0 Absent comments - 1 Comments read completely - ERAR_NO_MEMORY Not enough memory to extract comments - ERAR_BAD_DATA Broken comment - ERAR_UNKNOWN_FORMAT Unknown comment format - ERAR_SMALL_BUF Buffer too small, comments not completely read - -Return values -~~~~~~~~~~~~~ - - 0 Success - ERAR_END_ARCHIVE End of archive - ERAR_BAD_DATA File header broken - - -==================================================================== -int PASCAL RARReadHeaderEx(HANDLE hArcData, - struct RARHeaderDataEx *HeaderData) -==================================================================== - -Description -~~~~~~~~~~~ - Similar to RARReadHeader, but uses RARHeaderDataEx structure, -containing information about Unicode file names and 64 bit file sizes. - -struct RARHeaderDataEx -{ - char ArcName[1024]; - wchar_t ArcNameW[1024]; - char FileName[1024]; - wchar_t FileNameW[1024]; - unsigned int Flags; - unsigned int PackSize; - unsigned int PackSizeHigh; - unsigned int UnpSize; - unsigned int UnpSizeHigh; - unsigned int HostOS; - unsigned int FileCRC; - unsigned int FileTime; - unsigned int UnpVer; - unsigned int Method; - unsigned int FileAttr; - char *CmtBuf; - unsigned int CmtBufSize; - unsigned int CmtSize; - unsigned int CmtState; - unsigned int Reserved[1024]; -}; - - -==================================================================== -int PASCAL RARProcessFile(HANDLE hArcData, - int Operation, - char *DestPath, - char *DestName) -==================================================================== - -Description -~~~~~~~~~~~ - Performs action and moves the current position in the archive to - the next file. Extract or test the current file from the archive - opened in RAR_OM_EXTRACT mode. If the mode RAR_OM_LIST is set, - then a call to this function will simply skip the archive position - to the next file. - -Parameters -~~~~~~~~~~ -hArcData - This parameter should contain the archive handle obtained from the - RAROpenArchive function call. - -Operation - File operation. - - Possible values - - RAR_SKIP Move to the next file in the archive. If the - archive is solid and RAR_OM_EXTRACT mode was set - when the archive was opened, the current file will - be processed - the operation will be performed - slower than a simple seek. - - RAR_TEST Test the current file and move to the next file in - the archive. If the archive was opened with - RAR_OM_LIST mode, the operation is equal to - RAR_SKIP. - - RAR_EXTRACT Extract the current file and move to the next file. - If the archive was opened with RAR_OM_LIST mode, - the operation is equal to RAR_SKIP. - - -DestPath - This parameter should point to a zero terminated string containing the - destination directory to which to extract files to. If DestPath is equal - to NULL, it means extract to the current directory. This parameter has - meaning only if DestName is NULL. - -DestName - This parameter should point to a string containing the full path and name - to assign to extracted file or it can be NULL to use the default name. - If DestName is defined (not NULL), it overrides both the original file - name saved in the archive and path specigied in DestPath setting. - - Both DestPath and DestName must be in OEM encoding. If necessary, - use CharToOem to convert text to OEM before passing to this function. - -Return values -~~~~~~~~~~~~~ - 0 Success - ERAR_BAD_DATA File CRC error - ERAR_BAD_ARCHIVE Volume is not valid RAR archive - ERAR_UNKNOWN_FORMAT Unknown archive format - ERAR_EOPEN Volume open error - ERAR_ECREATE File create error - ERAR_ECLOSE File close error - ERAR_EREAD Read error - ERAR_EWRITE Write error - - -Note: if you wish to cancel extraction, return -1 when processing - UCM_PROCESSDATA callback message. - - -==================================================================== -int PASCAL RARProcessFileW(HANDLE hArcData, - int Operation, - wchar_t *DestPath, - wchar_t *DestName) -==================================================================== - -Description -~~~~~~~~~~~ - Unicode version of RARProcessFile. It uses Unicode DestPath - and DestName parameters, other parameters and return values - are the same as in RARProcessFile. - - -==================================================================== -void PASCAL RARSetCallback(HANDLE hArcData, - int PASCAL (*CallbackProc)(UINT msg,LPARAM UserData,LPARAM P1,LPARAM P2), - LPARAM UserData); -==================================================================== - -Description -~~~~~~~~~~~ - Set a user-defined callback function to process Unrar events. - -Parameters -~~~~~~~~~~ -hArcData - This parameter should contain the archive handle obtained from the - RAROpenArchive function call. - -CallbackProc - It should point to a user-defined callback function. - - The function will be passed four parameters: - - - msg Type of event. Described below. - - UserData User defined value passed to RARSetCallback. - - P1 and P2 Event dependent parameters. Described below. - - - Possible events - - UCM_CHANGEVOLUME Process volume change. - - P1 Points to the zero terminated name - of the next volume. - - P2 The function call mode: - - RAR_VOL_ASK Required volume is absent. The function should - prompt user and return a positive value - to retry or return -1 value to terminate - operation. The function may also specify a new - volume name, placing it to the address specified - by P1 parameter. - - RAR_VOL_NOTIFY Required volume is successfully opened. - This is a notification call and volume name - modification is not allowed. The function should - return a positive value to continue or -1 - to terminate operation. - - UCM_PROCESSDATA Process unpacked data. It may be used to read - a file while it is being extracted or tested - without actual extracting file to disk. - Return a positive value to continue process - or -1 to cancel the archive operation - - P1 Address pointing to the unpacked data. - Function may refer to the data but must not - change it. - - P2 Size of the unpacked data. It is guaranteed - only that the size will not exceed the maximum - dictionary size (4 Mb in RAR 3.0). - - UCM_NEEDPASSWORD DLL needs a password to process archive. - This message must be processed if you wish - to be able to handle archives with encrypted - file names. It can be also used as replacement - of RARSetPassword function even for usual - encrypted files with non-encrypted names. - - P1 Address pointing to the buffer for a password. - You need to copy a password here. - - P2 Size of the password buffer. - - -UserData - User data passed to callback function. - - Other functions of UnRAR.dll should not be called from the callback - function. - -Return values -~~~~~~~~~~~~~ - None - - - -==================================================================== -void PASCAL RARSetChangeVolProc(HANDLE hArcData, - int PASCAL (*ChangeVolProc)(char *ArcName,int Mode)); -==================================================================== - -Obsoleted, use RARSetCallback instead. - - - -==================================================================== -void PASCAL RARSetProcessDataProc(HANDLE hArcData, - int PASCAL (*ProcessDataProc)(unsigned char *Addr,int Size)) -==================================================================== - -Obsoleted, use RARSetCallback instead. - - -==================================================================== -void PASCAL RARSetPassword(HANDLE hArcData, - char *Password); -==================================================================== - -Description -~~~~~~~~~~~ - Set a password to decrypt files. - -Parameters -~~~~~~~~~~ -hArcData - This parameter should contain the archive handle obtained from the - RAROpenArchive function call. - -Password - It should point to a string containing a zero terminated password. - -Return values -~~~~~~~~~~~~~ - None - - -==================================================================== -void PASCAL RARGetDllVersion(); -==================================================================== - -Description -~~~~~~~~~~~ - Returns API version. - -Parameters -~~~~~~~~~~ - None. - -Return values -~~~~~~~~~~~~~ - Returns an integer value denoting UnRAR.dll API version, which is also -defined in unrar.h as RAR_DLL_VERSION. API version number is incremented -only in case of noticeable changes in UnRAR.dll API. Do not confuse it -with version of UnRAR.dll stored in DLL resources, which is incremented -with every DLL rebuild. - - If RARGetDllVersion() returns a value lower than UnRAR.dll which your -application was designed for, it may indicate that DLL version is too old -and it will fail to provide all necessary functions to your application. - - This function is absent in old versions of UnRAR.dll, so it is safer -to use LoadLibrary and GetProcAddress to access this function. - diff --git a/libs/unrar2/UnRARDLL/whatsnew.txt b/libs/unrar2/UnRARDLL/whatsnew.txt deleted file mode 100644 index 874d19b1..00000000 --- a/libs/unrar2/UnRARDLL/whatsnew.txt +++ /dev/null @@ -1,80 +0,0 @@ -List of unrar.dll API changes. We do not include performance and reliability -improvements into this list, but this library and RAR/UnRAR tools share -the same source code. So the latest version of unrar.dll usually contains -same decompression algorithm changes as the latest UnRAR version. -============================================================================ - --- 18 January 2008 - -all LONG parameters of CallbackProc function were changed -to LPARAM type for 64 bit mode compatibility. - - --- 12 December 2007 - -Added new RAR_OM_LIST_INCSPLIT open mode for function RAROpenArchive. - - --- 14 August 2007 - -Added NoCrypt\unrar_nocrypt.dll without decryption code for those -applications where presence of encryption or decryption code is not -allowed because of legal restrictions. - - --- 14 December 2006 - -Added ERAR_MISSING_PASSWORD error type. This error is returned -if empty password is specified for encrypted file. - - --- 12 June 2003 - -Added RARProcessFileW function, Unicode version of RARProcessFile - - --- 9 August 2002 - -Added RAROpenArchiveEx function allowing to specify Unicode archive -name and get archive flags. - - --- 24 January 2002 - -Added RARReadHeaderEx function allowing to read Unicode file names -and 64 bit file sizes. - - --- 23 January 2002 - -Added ERAR_UNKNOWN error type (it is used for all errors which -do not have special ERAR code yet) and UCM_NEEDPASSWORD callback -message. - -Unrar.dll automatically opens all next volumes not only when extracting, -but also in RAR_OM_LIST mode. - - --- 27 November 2001 - -RARSetChangeVolProc and RARSetProcessDataProc are replaced by -the single callback function installed with RARSetCallback. -Unlike old style callbacks, the new function accepts the user defined -parameter. Unrar.dll still supports RARSetChangeVolProc and -RARSetProcessDataProc for compatibility purposes, but if you write -a new application, better use RARSetCallback. - -File comments support is not implemented in the new DLL version yet. -Now CmtState is always 0. - - --- 13 August 2001 - -Added RARGetDllVersion function, so you may distinguish old unrar.dll, -which used C style callback functions and the new one with PASCAL callbacks. - - --- 10 May 2001 - -Callback functions in RARSetChangeVolProc and RARSetProcessDataProc -use PASCAL style call convention now. diff --git a/libs/unrar2/UnRARDLL/x64/readme.txt b/libs/unrar2/UnRARDLL/x64/readme.txt deleted file mode 100644 index bbfb340d..00000000 --- a/libs/unrar2/UnRARDLL/x64/readme.txt +++ /dev/null @@ -1 +0,0 @@ -This is x64 version of unrar.dll. diff --git a/libs/unrar2/UnRARDLL/x64/unrar64.lib b/libs/unrar2/UnRARDLL/x64/unrar64.lib deleted file mode 100644 index fd037919ee10c91665fa5f30e9ec6f37ac9e03c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3972 zcmcImJ5L)y5FTt^e(;c!Dj|{+Ath-DG3N`GqR0l5VDYldtHT_=3$}9D$TkUGIz*8! zb;?lEr=v!Sl){l{Qsh6-lUeWH-K@_T8+R=JFX0GJG)aPil0o?14WT1u~E*0RaD)!9rcy|$9c9u&)3SydpLU0z>YbQ{2D zVZq1O=GT`OvdP3+0y2rr%Dz!dU>FJ?4V<8`x1ViL%H6rcrdFs%yIHeTFtp!L@t7EYzHkb;~448R}+APB=S1U=xncFgNykIP$BP^!vZHz9Cb1ex(;lg;n&NAB^TZ`y9008Qv_ zur8+{+2dPer=4|DqS#t_cWpHdMl&VN91;nKHAG2Lg9OQ*)zQ&OX~1k#rj&Alj@ZGI zlno)g_N)?T*o1DHb&B6~N-MD``;^v?ypXDWTL|<3GLn}G-Pk$U9bXk)NMPfO)e&_Z zMrS4pI;QY=#2UJfjh`rgg~E$<9460%9iHC2V2Z#Mie<;NaR{Gzunu8%uo56nJ%xLt zn0D~1K7w}%*1p5gQXy9^RZ8!x;b)oW;l@3K>vR4ml53`RbY<7T{=&{NJ z8T{VurRFE@w#Af4)VkTX{rZuo&XgbZ#jzyD7qIBL_XE5H6A0N>>1QEQQ3Q7cd^KUsd!5{I~zUy7w?cE7ETsF~fJN4MR` z-rdP%UC3efzGO+NAFDqoRO#VZ@&!b}I?go3eo@Pw zv3G-qqtSb&xN%qfe|O$=y&Y@eo8QK> Y-(qC?Tll6l_w5P#7I3f{o_G}UA258WCIA2c diff --git a/libs/unrar2/license.txt b/libs/unrar2/license.txt deleted file mode 100644 index a395801b..00000000 --- a/libs/unrar2/license.txt +++ /dev/null @@ -1,21 +0,0 @@ -Copyright (c) 2003-2005 Jimmy Retzlaff, 2008 Konstantin Yegupov - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS -BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/libs/unrar2/unix.py b/libs/unrar2/unix.py index 9d87b18c..21f384cf 100644 --- a/libs/unrar2/unix.py +++ b/libs/unrar2/unix.py @@ -38,38 +38,38 @@ def call_unrar(params): "Calls rar/unrar command line executable, returns stdout pipe" global rar_executable_cached if rar_executable_cached is None: - for command in ('unrar', 'rar'): + for command in ('unrar', 'rar', os.path.join(os.path.dirname(__file__), 'unrar')): try: - subprocess.Popen([command], stdout=subprocess.PIPE) + subprocess.Popen([command], stdout = subprocess.PIPE) rar_executable_cached = command break except OSError: pass if rar_executable_cached is None: raise UnpackerNotInstalled("No suitable RAR unpacker installed") - + assert type(params) == list, "params must be list" args = [rar_executable_cached] + params try: gc.disable() # See http://bugs.python.org/issue1336 - return subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + return subprocess.Popen(args, stdout = subprocess.PIPE, stderr = subprocess.PIPE) finally: gc.enable() class RarFileImplementation(object): - def init(self, password=None): + def init(self, password = None): self.password = password - - + + stdoutdata, stderrdata = self.call('v', []).communicate() - + for line in stderrdata.splitlines(): if line.strip().startswith("Cannot open"): raise FileOpenError - if line.find("CRC failed")>=0: - raise IncorrectRARPassword + if line.find("CRC failed") >= 0: + raise IncorrectRARPassword accum = [] source = iter(stdoutdata.splitlines()) line = '' @@ -85,39 +85,39 @@ class RarFileImplementation(object): self.comment = '\n'.join(accum[:-1]) else: self.comment = None - + def escaped_password(self): return '-' if self.password == None else self.password - - - def call(self, cmd, options=[], files=[]): - options2 = options + ['p'+self.escaped_password()] - soptions = ['-'+x for x in options2] - return call_unrar([cmd]+soptions+['--',self.archiveName]+files) + + + def call(self, cmd, options = [], files = []): + options2 = options + ['p' + self.escaped_password()] + soptions = ['-' + x for x in options2] + return call_unrar([cmd] + soptions + ['--', self.archiveName] + files) def infoiter(self): - + stdoutdata, stderrdata = self.call('v', ['c-']).communicate() - + for line in stderrdata.splitlines(): if line.strip().startswith("Cannot open"): raise FileOpenError - + accum = [] source = iter(stdoutdata.splitlines()) line = '' while not line.startswith('--------------'): if line.strip().endswith('is not RAR archive'): raise InvalidRARArchive - if line.find("CRC failed")>=0: - raise IncorrectRARPassword + if line.find("CRC failed") >= 0: + raise IncorrectRARPassword line = source.next() line = source.next() i = 0 re_spaces = re.compile(r"\s+") while not line.startswith('--------------'): accum.append(line) - if len(accum)==2: + if len(accum) == 2: data = {} data['index'] = i data['filename'] = accum[0].strip() @@ -125,7 +125,7 @@ class RarFileImplementation(object): data['size'] = int(info[0]) attr = info[5] data['isdir'] = 'd' in attr.lower() - data['datetime'] = time.strptime(info[3]+" "+info[4], '%d-%m-%y %H:%M') + data['datetime'] = time.strptime(info[3] + " " + info[4], '%d-%m-%y %H:%M') data['comment'] = None yield data accum = [] @@ -136,12 +136,12 @@ class RarFileImplementation(object): res = [] for info in self.infoiter(): checkres = checker(info) - if checkres==True and not info.isdir: + if checkres == True and not info.isdir: pipe = self.call('p', ['inul'], [info.filename]).stdout res.append((info, pipe.read())) - return res + return res + - def extract(self, checker, path, withSubpath, overwrite): res = [] command = 'x' @@ -159,17 +159,17 @@ class RarFileImplementation(object): checkres = checker(info) if type(checkres) in [str, unicode]: raise NotImplementedError("Condition callbacks returning strings are deprecated and only supported in Windows") - if checkres==True and not info.isdir: + if checkres == True and not info.isdir: names.append(info.filename) res.append(info) names.append(path) proc = self.call(command, options, names) stdoutdata, stderrdata = proc.communicate() - if stderrdata.find("CRC failed")>=0: - raise IncorrectRARPassword - return res - + if stderrdata.find("CRC failed") >= 0: + raise IncorrectRARPassword + return res + def destruct(self): pass - + diff --git a/libs/unrar2/unrar b/libs/unrar2/unrar new file mode 100755 index 0000000000000000000000000000000000000000..3de167610fa729a05a45520e7c6447e2cb2d1954 GIT binary patch literal 234984 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

96X)o5>NPr6e5+061T>jX|_=cuKPSV7> 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

*}Eftb2sLyp~G5c#qRa&8(;X_ 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 literal 0 HcmV?d00001 diff --git a/libs/unrar2/UnRARDLL/unrar.dll b/libs/unrar2/unrar.dll similarity index 100% rename from libs/unrar2/UnRARDLL/unrar.dll rename to libs/unrar2/unrar.dll diff --git a/libs/unrar2/UnRARDLL/x64/unrar64.dll b/libs/unrar2/unrar64.dll similarity index 100% rename from libs/unrar2/UnRARDLL/x64/unrar64.dll rename to libs/unrar2/unrar64.dll diff --git a/libs/unrar2/windows.py b/libs/unrar2/windows.py index bb92481b..e249f8f3 100644 --- a/libs/unrar2/windows.py +++ b/libs/unrar2/windows.py @@ -23,10 +23,10 @@ # Low level interface - see UnRARDLL\UNRARDLL.TXT from __future__ import generators - -import ctypes, ctypes.wintypes -import os, os.path, sys -import Queue +from couchpotato.environment import Env +from shutil import copyfile +import ctypes.wintypes +import os.path import time from rar_exceptions import * @@ -64,13 +64,18 @@ UCM_NEEDPASSWORD = 2 architecture_bits = ctypes.sizeof(ctypes.c_voidp)*8 dll_name = "unrar.dll" if architecture_bits == 64: - dll_name = "x64\\unrar64.dll" - - -try: - unrar = ctypes.WinDLL(os.path.join(os.path.split(__file__)[0], 'UnRARDLL', dll_name)) -except WindowsError: - unrar = ctypes.WinDLL(dll_name) + dll_name = "unrar64.dll" + +# Copy dll first +dll_file = os.path.join(os.path.dirname(__file__), dll_name) +dll_copy = os.path.join(Env.get('cache_dir'), 'copied.dll') + +if os.path.isfile(dll_copy): + os.remove(dll_copy) + +copyfile(dll_file, dll_copy) + +unrar = ctypes.WinDLL(dll_copy) class RAROpenArchiveDataEx(ctypes.Structure): From f67c6fe8be26326de4fd03d78c15378513999f1d Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 20 Sep 2013 16:07:18 +0200 Subject: [PATCH 28/42] Only remove images from cache folder on cleanup --- couchpotato/core/plugins/file/main.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/couchpotato/core/plugins/file/main.py b/couchpotato/core/plugins/file/main.py index 238bc76c..fc63aca8 100644 --- a/couchpotato/core/plugins/file/main.py +++ b/couchpotato/core/plugins/file/main.py @@ -71,11 +71,11 @@ class FileManager(Plugin): db = get_session() for root, dirs, walk_files in os.walk(Env.get('cache_dir')): for filename in walk_files: - if root == python_cache or 'minified' in root or 'version' in filename or 'temp_updater' in root: continue - file_path = os.path.join(root, filename) - f = db.query(File).filter(File.path == toUnicode(file_path)).first() - if not f: - os.remove(file_path) + if os.path.splitext(filename)[1] in ['.png', '.jpg', '.jpeg']: + file_path = os.path.join(root, filename) + f = db.query(File).filter(File.path == toUnicode(file_path)).first() + if not f: + os.remove(file_path) except: log.error('Failed removing unused file: %s', traceback.format_exc()) From 9e0805ec89959f8f535afaec3606323b8c45ee68 Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 20 Sep 2013 18:08:12 +0200 Subject: [PATCH 29/42] Hide IE clear button on search --- couchpotato/core/media/movie/_base/static/search.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/couchpotato/core/media/movie/_base/static/search.css b/couchpotato/core/media/movie/_base/static/search.css index dc747346..80c18153 100644 --- a/couchpotato/core/media/movie/_base/static/search.css +++ b/couchpotato/core/media/movie/_base/static/search.css @@ -59,6 +59,11 @@ .search_form.shown .input input { opacity: 1; } + + .search_form input::-ms-clear { + width : 0; + height: 0; + } @media all and (max-width: 480px) { .search_form .input input { From 69a9fa11934dfea3b808c99e5aff0a54bdcb64a8 Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 20 Sep 2013 18:08:27 +0200 Subject: [PATCH 30/42] Simplify string before checking on imdb --- couchpotato/core/helpers/variable.py | 7 +++++-- couchpotato/core/plugins/scanner/main.py | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/couchpotato/core/helpers/variable.py b/couchpotato/core/helpers/variable.py index 1e7fc837..d93c9417 100644 --- a/couchpotato/core/helpers/variable.py +++ b/couchpotato/core/helpers/variable.py @@ -123,9 +123,12 @@ def cleanHost(host): return host -def getImdb(txt, check_inside = True, multiple = False): +def getImdb(txt, check_inside = False, multiple = False): - txt = ss(txt) + if not check_inside: + txt = simplifyString(txt) + else: + txt = ss(txt) if check_inside and os.path.isfile(txt): output = open(txt, 'r') diff --git a/couchpotato/core/plugins/scanner/main.py b/couchpotato/core/plugins/scanner/main.py index ff17b647..0662d008 100644 --- a/couchpotato/core/plugins/scanner/main.py +++ b/couchpotato/core/plugins/scanner/main.py @@ -565,7 +565,7 @@ class Scanner(Plugin): if not imdb_id: try: for nf in files['nfo']: - imdb_id = getImdb(nf) + imdb_id = getImdb(nf, check_inside = True) if imdb_id: log.debug('Found movie via nfo file: %s', nf) nfo_file = nf @@ -578,7 +578,7 @@ class Scanner(Plugin): try: for filetype in files: for filetype_file in files[filetype]: - imdb_id = getImdb(filetype_file, check_inside = False) + imdb_id = getImdb(filetype_file) if imdb_id: log.debug('Found movie via imdb in filename: %s', nfo_file) break From bfa3b871889bc6fbc630b15adaae9cc7cb0d4acd Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 21 Sep 2013 11:07:16 +0200 Subject: [PATCH 31/42] Only show soon and late with no releases --- couchpotato/core/plugins/dashboard/main.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/couchpotato/core/plugins/dashboard/main.py b/couchpotato/core/plugins/dashboard/main.py index 2da4d8cc..f006ac41 100644 --- a/couchpotato/core/plugins/dashboard/main.py +++ b/couchpotato/core/plugins/dashboard/main.py @@ -4,9 +4,10 @@ from couchpotato.core.event import fireEvent from couchpotato.core.helpers.variable import splitString, tryInt from couchpotato.core.logger import CPLog from couchpotato.core.plugins.base import Plugin -from couchpotato.core.settings.model import Movie, Library, LibraryTitle +from couchpotato.core.settings.model import Movie, Library, LibraryTitle, \ + Release from sqlalchemy.orm import joinedload_all -from sqlalchemy.sql.expression import asc +from sqlalchemy.sql.expression import asc, or_ import random as rndm import time @@ -48,12 +49,14 @@ class Dashboard(Plugin): limit = tryInt(splt[0]) # Get all active movies - active_status = fireEvent('status.get', ['active'], single = True) + active_status, ignored_status = fireEvent('status.get', ['active', 'ignored'], single = True) q = db.query(Movie) \ .join(Library) \ + .outerjoin(Movie.releases) \ .filter(Movie.status_id == active_status.get('id')) \ .with_entities(Movie.id, Movie.profile_id, Library.info, Library.year) \ - .group_by(Movie.id) + .group_by(Movie.id) \ + .filter(or_(Release.id == None, Release.status_id == ignored_status.get('id'))) if not random: q = q.join(LibraryTitle) \ From 6cd38a346995f13bf25b5d59cb9bc71aa5c45930 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 21 Sep 2013 11:20:53 +0200 Subject: [PATCH 32/42] Providers missing in wizard --- couchpotato/core/plugins/wizard/static/wizard.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/couchpotato/core/plugins/wizard/static/wizard.js b/couchpotato/core/plugins/wizard/static/wizard.js index 71910e27..3aa942d2 100644 --- a/couchpotato/core/plugins/wizard/static/wizard.js +++ b/couchpotato/core/plugins/wizard/static/wizard.js @@ -24,9 +24,10 @@ Page.Wizard = new Class({ 'title': 'What download apps are you using?', 'description': 'CP needs an external download app to work with. Choose one below. For more downloaders check settings after you have filled in the wizard. If your download app isn\'t in the list, use the default Blackhole.' }, - 'providers': { + 'searcher': { + 'label': 'Providers', 'title': 'Are you registered at any of these sites?', - 'description': 'CP uses these sites to search for movies. A few free are enabled by default, but it\'s always better to have a few more. Check settings for the full list of available providers.' + 'description': 'CP uses these sites to search for movies. A few free are enabled by default, but it\'s always better to have more.' }, 'renamer': { 'title': 'Move & rename the movies after downloading?', @@ -76,7 +77,7 @@ Page.Wizard = new Class({ ) } }, - groups: ['welcome', 'general', 'downloaders', 'searcher', 'providers', 'renamer', 'automation', 'finish'], + groups: ['welcome', 'general', 'downloaders', 'searcher', 'renamer', 'automation', 'finish'], open: function(action, params){ var self = this; @@ -195,8 +196,7 @@ Page.Wizard = new Class({ self.el.getElement('.advanced_toggle').destroy(); // Hide retention - self.el.getElement('.tab_searcher').hide(); - self.el.getElement('.t_searcher').hide(); + self.el.getElement('.section_nzb').hide(); // Add pointer new Element('.tab_wrapper').wraps(tabs); From fdd851d29a1cf96054f569d9b85389ddd64f33fd Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 21 Sep 2013 12:14:40 +0200 Subject: [PATCH 33/42] Binsearch age parse failed for release new than 1 day. fix #2217 --- couchpotato/core/providers/nzb/binsearch/main.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/couchpotato/core/providers/nzb/binsearch/main.py b/couchpotato/core/providers/nzb/binsearch/main.py index dee5fc78..770ed50a 100644 --- a/couchpotato/core/providers/nzb/binsearch/main.py +++ b/couchpotato/core/providers/nzb/binsearch/main.py @@ -56,6 +56,10 @@ class BinSearch(NZBProvider): info = row.find('span', attrs = {'class':'d'}) size_match = re.search('size:.(?P[0-9\.]+.[GMB]+)', info.text) + age = 0 + try: age = re.search('(?P\d+d)', row.find_all('td')[-1:][0].text).group('size')[:-1] + except: pass + def extra_check(item): parts = re.search('available:.(?P\d+)./.(?P\d+)', info.text) total = tryInt(parts.group('total')) @@ -74,7 +78,7 @@ class BinSearch(NZBProvider): results.append({ 'id': nzb_id, 'name': title.text, - 'age': tryInt(re.search('(?P\d+d)', row.find_all('td')[-1:][0].text).group('size')[:-1]), + 'age': tryInt(age), 'size': self.parseSize(size_match.group('size')), 'url': self.urls['download'] % nzb_id, 'detail_url': self.urls['detail'] % info.find('a')['href'], From 19f782e4a53b940345f89a10fa5e946dc92aee21 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 21 Sep 2013 12:41:06 +0200 Subject: [PATCH 34/42] Don't try to change elements that don't exist. fix #2219 --- .../media/movie/_base/static/movie.actions.js | 26 ++++++++++++------- .../core/media/movie/_base/static/movie.js | 18 +++++++------ 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/couchpotato/core/media/movie/_base/static/movie.actions.js b/couchpotato/core/media/movie/_base/static/movie.actions.js index 24bf6260..e9f6141f 100644 --- a/couchpotato/core/media/movie/_base/static/movie.actions.js +++ b/couchpotato/core/media/movie/_base/static/movie.actions.js @@ -18,11 +18,13 @@ var MovieAction = new Class({ create: function(){}, disable: function(){ - this.el.addClass('disable') + if(this.el) + this.el.addClass('disable') }, enable: function(){ - this.el.removeClass('disable') + if(this.el) + this.el.removeClass('disable') }, getTitle: function(){ @@ -252,10 +254,10 @@ MA.Release = new Class({ }); if(self.last_release) - self.release_container.getElement('#release_'+self.last_release.id).addClass('last_release'); + self.release_container.getElements('#release_'+self.last_release.id).addClass('last_release'); if(self.next_release) - self.release_container.getElement('#release_'+self.next_release.id).addClass('next_release'); + self.release_container.getElements('#release_'+self.next_release.id).addClass('next_release'); if(self.next_release || (self.last_release && ['ignored', 'failed'].indexOf(self.last_release.status.identifier) === false)){ @@ -365,21 +367,25 @@ MA.Release = new Class({ var release_el = self.release_container.getElement('#release_'+release.id), icon = release_el.getElement('.download.icon2'); - icon.addClass('icon spinner').removeClass('download'); + if(icon) + icon.addClass('icon spinner').removeClass('download'); Api.request('release.download', { 'data': { 'id': release.id }, 'onComplete': function(json){ - icon.removeClass('icon spinner'); + if(icon) + icon.removeClass('icon spinner'); if(json.success){ - icon.addClass('completed'); + if(icon) + icon.addClass('completed'); release_el.getElement('.release_status').set('text', 'snatched'); } else - icon.addClass('attention').set('title', 'Something went wrong when downloading, please check logs.'); + if(icon) + icon.addClass('attention').set('title', 'Something went wrong when downloading, please check logs.'); } }); }, @@ -393,11 +399,11 @@ MA.Release = new Class({ }, 'onComplete': function(){ var el = release.el; - if(el.hasClass('failed') || el.hasClass('ignored')){ + if(el && (el.hasClass('failed') || el.hasClass('ignored'))){ el.removeClass('failed').removeClass('ignored'); el.getElement('.release_status').set('text', 'available'); } - else { + else if(el) { el.addClass('ignored'); el.getElement('.release_status').set('text', 'ignored'); } diff --git a/couchpotato/core/media/movie/_base/static/movie.js b/couchpotato/core/media/movie/_base/static/movie.js index 363d860c..6defc2ad 100644 --- a/couchpotato/core/media/movie/_base/static/movie.js +++ b/couchpotato/core/media/movie/_base/static/movie.js @@ -181,18 +181,18 @@ var Movie = new Class({ // Add releases if(self.data.releases) self.data.releases.each(function(release){ - + var q = self.quality.getElement('.q_id'+ release.quality_id), status = Status.get(release.status_id); - + if(!q && (status.identifier == 'snatched' || status.identifier == 'done')) var q = self.addQuality(release.quality_id) - + if (status && q && !q.hasClass(status.identifier)){ q.addClass(status.identifier); q.set('title', (q.get('title') ? q.get('title') : '') + ' status: '+ status.label) } - + }); Object.each(self.options.actions, function(action, key){ @@ -256,7 +256,8 @@ var Movie = new Class({ self.el.removeEvents('outerClick') setTimeout(function(){ - self.el.getElements('> :not(.data):not(.poster):not(.movie_container)').hide(); + if(self.el) + self.el.getElements('> :not(.data):not(.poster):not(.movie_container)').hide(); }, 600); self.data_container.removeClass('hide_right'); @@ -266,9 +267,10 @@ var Movie = new Class({ changeView: function(new_view){ var self = this; - self.el - .removeClass(self.view+'_view') - .addClass(new_view+'_view') + if(self.el) + self.el + .removeClass(self.view+'_view') + .addClass(new_view+'_view') self.view = new_view; }, From 2d3fc03a0047f2c72a312af7f37671c760805252 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 21 Sep 2013 13:56:17 +0200 Subject: [PATCH 35/42] Revert back to UTF8 when ss encoding fails. fix #2220 --- couchpotato/core/helpers/encoding.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/couchpotato/core/helpers/encoding.py b/couchpotato/core/helpers/encoding.py index 6e864446..5fa2e2ad 100644 --- a/couchpotato/core/helpers/encoding.py +++ b/couchpotato/core/helpers/encoding.py @@ -38,8 +38,14 @@ def toUnicode(original, *args): return toUnicode(ascii_text) def ss(original, *args): - from couchpotato.environment import Env - return toUnicode(original, *args).encode(Env.get('encoding')) + + u_original = toUnicode(original, *args) + try: + from couchpotato.environment import Env + return u_original.encode(Env.get('encoding')) + except Exception, e: + log.debug('Failed ss encoding char, force UTF8: %s', e) + return u_original.encode('UTF-8') def ek(original, *args): if isinstance(original, (str, unicode)): From 866d9621cb81f1ae1a7f226afc9ae831233dce7c Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 21 Sep 2013 22:16:44 +0200 Subject: [PATCH 36/42] Create new listener list --- couchpotato/core/notifications/core/main.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/couchpotato/core/notifications/core/main.py b/couchpotato/core/notifications/core/main.py index a9a20b0a..04acf284 100644 --- a/couchpotato/core/notifications/core/main.py +++ b/couchpotato/core/notifications/core/main.py @@ -198,13 +198,16 @@ class CoreNotifier(Notification): def removeListener(self, callback): self.m_lock.acquire() + new_listeners = [] for list_tuple in self.listeners: try: listener, last_id = list_tuple - if listener == callback: - self.listeners.remove(list_tuple) + if listener != callback: + new_listeners.append(list_tuple) except: log.debug('Failed removing listener: %s', traceback.format_exc()) + + self.listeners = new_listeners self.m_lock.release() def cleanMessages(self): From d70a71a12e72ebb0eca21a645dc49f5b15b82277 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 21 Sep 2013 22:17:01 +0200 Subject: [PATCH 37/42] Make nonblock debug message --- couchpotato/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/api.py b/couchpotato/api.py index b6135583..6cb227da 100644 --- a/couchpotato/api.py +++ b/couchpotato/api.py @@ -49,7 +49,7 @@ class NonBlockHandler(RequestHandler): try: self.finish(response) except: - log.error('Failed doing nonblock request: %s', (traceback.format_exc())) + log.debug('Failed doing nonblock request, probably already closed: %s', (traceback.format_exc())) try: self.finish({'success': False, 'error': 'Failed returning results'}) except: pass From 1ff4901846686ece84d4ca2ca367838bb5482415 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 21 Sep 2013 22:29:15 +0200 Subject: [PATCH 38/42] Make sure to remove listener, even after fail --- couchpotato/api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/couchpotato/api.py b/couchpotato/api.py index 6cb227da..091de42a 100644 --- a/couchpotato/api.py +++ b/couchpotato/api.py @@ -44,6 +44,7 @@ class NonBlockHandler(RequestHandler): def onNewMessage(self, response): if self.request.connection.stream.closed(): + self.on_connection_close() return try: From 03700e0a042f959be57fd5eeefb7c03b582b4115 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 22 Sep 2013 00:43:50 +0200 Subject: [PATCH 39/42] Userscript image didn't show --- couchpotato/core/plugins/userscript/static/userscript.js | 2 +- couchpotato/core/plugins/wizard/static/wizard.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/couchpotato/core/plugins/userscript/static/userscript.js b/couchpotato/core/plugins/userscript/static/userscript.js index b7849766..2aeb7b5f 100644 --- a/couchpotato/core/plugins/userscript/static/userscript.js +++ b/couchpotato/core/plugins/userscript/static/userscript.js @@ -96,7 +96,7 @@ var UserscriptSettingTab = new Class({ }) ) ).setStyles({ - 'background-image': "url('"+Api.createUrl('static/userscript/userscript.png')+"')" + 'background-image': "url('"+App.createUrl('static/plugin/userscript/userscript.png')+"')" }); }); diff --git a/couchpotato/core/plugins/wizard/static/wizard.js b/couchpotato/core/plugins/wizard/static/wizard.js index 3aa942d2..b4857abb 100644 --- a/couchpotato/core/plugins/wizard/static/wizard.js +++ b/couchpotato/core/plugins/wizard/static/wizard.js @@ -39,7 +39,7 @@ Page.Wizard = new Class({ '
Once installed, just click the bookmarklet on a movie page and watch the magic happen ;)', 'content': function(){ return App.createUserscriptButtons().setStyles({ - 'background-image': "url('"+Api.createUrl('static/userscript/userscript.png')+"')" + 'background-image': "url('"+App.createUrl('static/plugin/userscript/userscript.png')+"')" }) } }, From 2365e1859f60c8ab8ca0f10c8113c8c0949341ae Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 22 Sep 2013 10:47:13 +0200 Subject: [PATCH 40/42] Don't show suggestions if there aren't any. fix #2153 --- couchpotato/core/plugins/suggestion/main.py | 2 +- .../core/plugins/suggestion/static/suggest.js | 89 ++++++++++--------- 2 files changed, 48 insertions(+), 43 deletions(-) diff --git a/couchpotato/core/plugins/suggestion/main.py b/couchpotato/core/plugins/suggestion/main.py index a7d0f82a..eb31d26e 100644 --- a/couchpotato/core/plugins/suggestion/main.py +++ b/couchpotato/core/plugins/suggestion/main.py @@ -102,6 +102,6 @@ class Suggestion(Plugin): if suggestions: new_suggestions.extend(suggestions) - self.setCache('suggestion_cached', new_suggestions, timeout = 6048000) + self.setCache('suggestion_cached', new_suggestions, timeout = 3024000) return new_suggestions diff --git a/couchpotato/core/plugins/suggestion/static/suggest.js b/couchpotato/core/plugins/suggestion/static/suggest.js index e6226711..40fe53b9 100644 --- a/couchpotato/core/plugins/suggestion/static/suggest.js +++ b/couchpotato/core/plugins/suggestion/static/suggest.js @@ -58,54 +58,59 @@ var SuggestList = new Class({ var self = this; - if(!json) return; + if(!json || json.count == 0){ + self.el.hide(); + } + else { - Object.each(json.suggestions, function(movie){ + Object.each(json.suggestions, function(movie){ - var m = new Block.Search.Item(movie, { - 'onAdded': function(){ - self.afterAdded(m, movie) - } - }); - m.data_container.grab( - new Element('div.actions').adopt( - new Element('a.add.icon2', { - 'title': 'Add movie with your default quality', - 'data-add': movie.imdb, - 'events': { - 'click': m.showOptions.bind(m) - } - }), - $(new MA.IMDB(m)), - $(new MA.Trailer(m, { - 'height': 150 - })), - new Element('a.delete.icon2', { - 'title': 'Don\'t suggest this movie again', - 'data-ignore': movie.imdb - }), - new Element('a.eye-open.icon2', { - 'title': 'Seen it, like it, don\'t add', - 'data-seen': movie.imdb - }) + var m = new Block.Search.Item(movie, { + 'onAdded': function(){ + self.afterAdded(m, movie) + } + }); + m.data_container.grab( + new Element('div.actions').adopt( + new Element('a.add.icon2', { + 'title': 'Add movie with your default quality', + 'data-add': movie.imdb, + 'events': { + 'click': m.showOptions.bind(m) + } + }), + $(new MA.IMDB(m)), + $(new MA.Trailer(m, { + 'height': 150 + })), + new Element('a.delete.icon2', { + 'title': 'Don\'t suggest this movie again', + 'data-ignore': movie.imdb + }), + new Element('a.eye-open.icon2', { + 'title': 'Seen it, like it, don\'t add', + 'data-seen': movie.imdb + }) + ) + ); + m.data_container.removeEvents('click'); + + // Add rating + m.info_container.adopt( + m.rating = m.info.rating && m.info.rating.imdb.length == 2 && parseFloat(m.info.rating.imdb[0]) > 0 ? new Element('span.rating', { + 'text': parseFloat(m.info.rating.imdb[0]), + 'title': parseInt(m.info.rating.imdb[1]) + ' votes' + }) : null, + m.genre = m.info.genres && m.info.genres.length > 0 ? new Element('span.genres', { + 'text': m.info.genres.slice(0, 3).join(', ') + }) : null ) - ); - m.data_container.removeEvents('click'); - // Add rating - m.info_container.adopt( - m.rating = m.info.rating && m.info.rating.imdb.length == 2 && parseFloat(m.info.rating.imdb[0]) > 0 ? new Element('span.rating', { - 'text': parseFloat(m.info.rating.imdb[0]), - 'title': parseInt(m.info.rating.imdb[1]) + ' votes' - }) : null, - m.genre = m.info.genres && m.info.genres.length > 0 ? new Element('span.genres', { - 'text': m.info.genres.slice(0, 3).join(', ') - }) : null - ) + $(m).inject(self.el); - $(m).inject(self.el); + }); - }); + } self.fireEvent('loaded'); From cc3aad49ed172ef997f219a860532f8a8f398b7f Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 23 Sep 2013 21:35:29 +0200 Subject: [PATCH 41/42] Remove FTDWorld --- .../core/providers/nzb/ftdworld/__init__.py | 40 --------- .../core/providers/nzb/ftdworld/main.py | 83 ------------------- 2 files changed, 123 deletions(-) delete mode 100644 couchpotato/core/providers/nzb/ftdworld/__init__.py delete mode 100644 couchpotato/core/providers/nzb/ftdworld/main.py diff --git a/couchpotato/core/providers/nzb/ftdworld/__init__.py b/couchpotato/core/providers/nzb/ftdworld/__init__.py deleted file mode 100644 index 5a004a70..00000000 --- a/couchpotato/core/providers/nzb/ftdworld/__init__.py +++ /dev/null @@ -1,40 +0,0 @@ -from .main import FTDWorld - -def start(): - return FTDWorld() - -config = [{ - 'name': 'ftdworld', - 'groups': [ - { - 'tab': 'searcher', - 'list': 'nzb_providers', - 'name': 'FTDWorld', - 'description': 'Free provider, less accurate. See FTDWorld', - 'wizard': True, - 'options': [ - { - 'name': 'enabled', - 'type': 'enabler', - }, - { - 'name': 'username', - 'default': '', - }, - { - 'name': 'password', - 'default': '', - 'type': 'password', - }, - { - 'name': 'extra_score', - 'advanced': True, - 'label': 'Extra Score', - 'type': 'int', - 'default': 0, - 'description': 'Starting score for each release found via this provider.', - } - ], - }, - ], -}] diff --git a/couchpotato/core/providers/nzb/ftdworld/main.py b/couchpotato/core/providers/nzb/ftdworld/main.py deleted file mode 100644 index 9940cee6..00000000 --- a/couchpotato/core/providers/nzb/ftdworld/main.py +++ /dev/null @@ -1,83 +0,0 @@ -from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode -from couchpotato.core.helpers.variable import tryInt -from couchpotato.core.logger import CPLog -from couchpotato.core.providers.nzb.base import NZBProvider -from couchpotato.environment import Env -import json -import traceback - -log = CPLog(__name__) - - -class FTDWorld(NZBProvider): - - urls = { - 'search': 'http://ftdworld.net/api/index.php?%s', - 'detail': 'http://ftdworld.net/spotinfo.php?id=%s', - 'download': 'http://ftdworld.net/cgi-bin/nzbdown.pl?fileID=%s', - 'login': 'http://ftdworld.net/api/login.php', - 'login_check': 'http://ftdworld.net/api/login.php', - } - - http_time_between_calls = 3 #seconds - - cat_ids = [ - ([4, 11], ['dvdr']), - ([1], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr', 'brrip']), - ([7, 10, 13, 14], ['bd50', '720p', '1080p']), - ] - cat_backup_id = 1 - - def _searchOnTitle(self, title, movie, quality, results): - - q = '"%s" %s' % (title, movie['library']['year']) - - params = tryUrlencode({ - 'ctitle': q, - 'customQuery': 'usr', - 'cage': Env.setting('retention', 'nzb'), - 'csizemin': quality.get('size_min'), - 'csizemax': quality.get('size_max'), - 'ccategory': 14, - 'ctype': ','.join([str(x) for x in self.getCatId(quality['identifier'])]), - }) - - data = self.getJsonData(self.urls['search'] % params, opener = self.login_opener) - - if data: - try: - - if data.get('numRes') == 0: - return - - for item in data.get('data'): - - nzb_id = tryInt(item.get('id')) - results.append({ - 'id': nzb_id, - 'name': toUnicode(item.get('Title')), - 'age': self.calculateAge(tryInt(item.get('Created'))), - 'size': item.get('Size', 0), - 'url': self.urls['download'] % nzb_id, - 'detail_url': self.urls['detail'] % nzb_id, - 'score': (tryInt(item.get('webPlus', 0)) - tryInt(item.get('webMin', 0))) * 3, - }) - - except: - log.error('Failed to parse HTML response from FTDWorld: %s', traceback.format_exc()) - - def getLoginParams(self): - return tryUrlencode({ - 'userlogin': self.conf('username'), - 'passlogin': self.conf('password'), - 'submit': 'Log In', - }) - - def loginSuccess(self, output): - try: - return json.loads(output).get('goodToGo', False) - except: - return False - - loginCheckSuccess = loginSuccess - From b5d2a41d60097d42ba610e0e6a12408b6c2726c5 Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 23 Sep 2013 21:35:40 +0200 Subject: [PATCH 42/42] Enable NewzNab bij default --- couchpotato/core/providers/nzb/newznab/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/couchpotato/core/providers/nzb/newznab/__init__.py b/couchpotato/core/providers/nzb/newznab/__init__.py index 3902ab13..54359275 100644 --- a/couchpotato/core/providers/nzb/newznab/__init__.py +++ b/couchpotato/core/providers/nzb/newznab/__init__.py @@ -20,6 +20,7 @@ config = [{ { 'name': 'enabled', 'type': 'enabler', + 'default': True, }, { 'name': 'use',