diff --git a/README.md b/README.md index 4dbe75bb..a38c0520 100644 --- a/README.md +++ b/README.md @@ -29,17 +29,21 @@ OS X: * Then do `python CouchPotatoServer/CouchPotato.py` * Your browser should open up, but if it doesn't go to `http://localhost:5050/` -Linux (Ubuntu / Debian): +Linux: -* Install [GIT](http://git-scm.com/) with `apt-get install git-core` +* (Ubuntu / Debian) Install [GIT](http://git-scm.com/) with `apt-get install git-core` +* (Fedora / CentOS) Install [GIT](http://git-scm.com/) with `yum install git` * 'cd' to the folder of your choosing. * Run `git clone https://github.com/RuudBurger/CouchPotatoServer.git` * Then do `python CouchPotatoServer/CouchPotato.py` to start -* To run on boot copy the init script `sudo cp CouchPotatoServer/init/ubuntu /etc/init.d/couchpotato` -* Copy the default paths file `sudo cp CouchPotatoServer/init/ubuntu.default /etc/default/couchpotato` -* Change the paths inside the default file `sudo nano /etc/default/couchpotato` -* Make it executable `sudo chmod +x /etc/init.d/couchpotato` -* Add it to defaults `sudo update-rc.d couchpotato defaults` +* (Ubuntu / Debian) To run on boot copy the init script `sudo cp CouchPotatoServer/init/ubuntu /etc/init.d/couchpotato` +* (Ubuntu / Debian) Copy the default paths file `sudo cp CouchPotatoServer/init/ubuntu.default /etc/default/couchpotato` +* (Ubuntu / Debian) Change the paths inside the default file `sudo nano /etc/default/couchpotato` +* (Ubuntu / Debian) Make it executable `sudo chmod +x /etc/init.d/couchpotato` +* (Ubuntu / Debian) Add it to defaults `sudo update-rc.d couchpotato defaults` +* (systemd) To run on boot copy the systemd config `sudo cp CouchPotatoServer/init/couchpotato.fedora.service /etc/systemd/system/couchpotato.service` +* (systemd) Update the systemd config file with your user and path to CouchPotato.py +* (systemd) Enable it at boot with `sudo systemctl enable couchpotato` * Open your browser and go to `http://localhost:5050/` diff --git a/contributing.md b/contributing.md index 821212c8..3bd42c00 100644 --- a/contributing.md +++ b/contributing.md @@ -13,6 +13,8 @@ Lastly, for anything related to CouchPotato, feel free to stop by the [forum](ht ## Issues Issues are intended for reporting bugs and weird behaviour or suggesting improvements to CouchPotatoServer. Before you submit an issue, please go through the following checklist: + * **FILL IN ALL THE FIELDS ASKED FOR** + * **POST MORE THAN A SINGLE LINE LOG**, if you do, you'd better have a easy reproducable bug * Search through existing issues (*including closed issues!*) first: you might be able to get your answer there. * Double check your issue manually, because it could be an external issue. * Post logs with your issue: Without seeing what is going on, the developers can't reproduce the error. @@ -25,12 +27,14 @@ Before you submit an issue, please go through the following checklist: * What hardware / OS are you using and what are its limitations? For example: NAS can be slow and maybe have a different version of python installed than when you use CP on OS X or Windows. * Your issue might be marked with the "can't reproduce" tag. Don't ask why your issue was closed if it says so in the tag. * If you're running on a NAS (QNAP, Austor, Synology etc.) with pre-made packages, make sure these are set up to use our source repository (RuudBurger/CouchPotatoServer) and nothing else! + * Do not "bump" issues with "Any updates on this" or whatever. Yes I've seen it, you don't have to remind me of it. There will be an update when the code is done or I need information. If you feel the need to do so, you'd better have more info on the issue. The more relevant information you provide, the more likely that your issue will be resolved. +If you don't follow any of the checks above, I'll close the issue. If you are wondering why (and ask) I'll block you from posting new issues and the repo. ## Pull Requests Pull requests are intended for contributing code or documentation to the project. Before you submit a pull request, consider the following: * Make sure your pull request is made for the *develop* branch (or relevant feature branch). * Have you tested your PR? If not, why? - * Does your PR have any limitations we should know of? + * Does your PR have any limitations I should know of? * Is your PR up-to-date with the branch you're trying to push into? diff --git a/couchpotato/core/_base/updater/main.py b/couchpotato/core/_base/updater/main.py index 9f6613ab..7730d3bd 100644 --- a/couchpotato/core/_base/updater/main.py +++ b/couchpotato/core/_base/updater/main.py @@ -143,7 +143,7 @@ class Updater(Plugin): } def doShutdown(self, *args, **kwargs): - if not Env.get('dev'): + if not Env.get('dev') and not Env.get('desktop'): removePyc(Env.get('app_dir'), show_logs = False) return super(Updater, self).doShutdown(*args, **kwargs) diff --git a/couchpotato/core/database.py b/couchpotato/core/database.py index 47be7f53..10ae26c2 100644 --- a/couchpotato/core/database.py +++ b/couchpotato/core/database.py @@ -238,6 +238,7 @@ class Database(object): old_db = os.path.join(Env.get('data_dir'), 'couchpotato.db.old') if not os.path.isdir(failed_location) and os.path.isfile(old_db): + log.error('Corrupt database, trying migrate again') db.close() # Rename database folder @@ -331,6 +332,8 @@ class Database(object): log.info('Getting data took %s', time.time() - migrate_start) db = self.getDB() + if not db.opened: + return # Use properties properties = migrate_data['properties'] diff --git a/couchpotato/core/downloaders/transmission.py b/couchpotato/core/downloaders/transmission.py index d941cca6..409efaa0 100644 --- a/couchpotato/core/downloaders/transmission.py +++ b/couchpotato/core/downloaders/transmission.py @@ -23,16 +23,14 @@ class Transmission(DownloaderBase): log = CPLog(__name__) trpc = None - def connect(self, reconnect = False): + def connect(self): # Load host from config and split out port. host = cleanHost(self.conf('host'), protocol = False).split(':') if not isInt(host[1]): log.error('Config properties are not filled in correctly, port is missing.') return False - if not self.trpc or reconnect: - self.trpc = TransmissionRPC(host[0], port = host[1], rpc_url = self.conf('rpc_url').strip('/ '), username = self.conf('username'), password = self.conf('password')) - + self.trpc = TransmissionRPC(host[0], port = host[1], rpc_url = self.conf('rpc_url').strip('/ '), username = self.conf('username'), password = self.conf('password')) return self.trpc def download(self, data = None, media = None, filedata = None): @@ -88,7 +86,7 @@ class Transmission(DownloaderBase): return self.downloadReturnId(remote_torrent['torrent-added']['hashString']) def test(self): - if self.connect(True) and self.trpc.get_session(): + if self.connect() and self.trpc.get_session(): return True return False diff --git a/couchpotato/core/media/_base/media/main.py b/couchpotato/core/media/_base/media/main.py index 1d3e1530..036badca 100644 --- a/couchpotato/core/media/_base/media/main.py +++ b/couchpotato/core/media/_base/media/main.py @@ -4,7 +4,7 @@ import time import traceback from string import ascii_lowercase -from CodernityDB.database import RecordNotFound +from CodernityDB.database import RecordNotFound, RecordDeleted from couchpotato import tryInt, get_db from couchpotato.api import addApiView from couchpotato.core.event import fireEvent, fireEventAsync, addEvent @@ -146,7 +146,7 @@ class MediaPlugin(MediaBase): return media - except RecordNotFound: + except (RecordNotFound, RecordDeleted): log.error('Media with id "%s" not found', media_id) except: raise @@ -415,7 +415,7 @@ class MediaPlugin(MediaBase): db.delete(release) total_deleted += 1 - if (total_releases == total_deleted and media['status'] != 'active') or (total_releases == 0 and not new_media_status) or (not new_media_status and delete_from == 'late'): + if (total_releases == total_deleted) or (total_releases == 0 and not new_media_status) or (not new_media_status and delete_from == 'late'): db.delete(media) deleted = True elif new_media_status: @@ -488,18 +488,21 @@ class MediaPlugin(MediaBase): db.update(m) # Tag media as recent - self.tag(media_id, 'recent') + self.tag(media_id, 'recent', update_edited = True) return m['status'] except: log.error('Failed restatus: %s', traceback.format_exc()) - def tag(self, media_id, tag): + def tag(self, media_id, tag, update_edited = False): try: db = get_db() m = db.get('id', media_id) + if update_edited: + m['last_edit'] = int(time.time()) + tags = m.get('tags') or [] if tag not in tags: tags.append(tag) diff --git a/couchpotato/core/media/_base/providers/nzb/newznab.py b/couchpotato/core/media/_base/providers/nzb/newznab.py index 7db7e865..81622ade 100644 --- a/couchpotato/core/media/_base/providers/nzb/newznab.py +++ b/couchpotato/core/media/_base/providers/nzb/newznab.py @@ -45,7 +45,7 @@ class Base(NZBProvider, RSS): def _searchOnHost(self, host, media, quality, results): query = self.buildUrl(media, host) - url = '%s&%s' % (self.getUrl(host['host']), query) + url = '%s%s' % (self.getUrl(host['host']), query) nzbs = self.getRSSData(url, cache_timeout = 1800, headers = {'User-Agent': Env.getIdentifier()}) for nzb in nzbs: @@ -83,7 +83,7 @@ class Base(NZBProvider, RSS): try: # Get details for extended description to retrieve passwords query = self.buildDetailsUrl(nzb_id, host['api_key']) - url = '%s&%s' % (self.getUrl(host['host']), query) + url = '%s%s' % (self.getUrl(host['host']), query) nzb_details = self.getRSSData(url, cache_timeout = 1800, headers = {'User-Agent': Env.getIdentifier()})[0] description = self.getTextElement(nzb_details, 'description') @@ -220,7 +220,7 @@ config = [{ 'description': 'Enable NewzNab such as NZB.su, \ NZBs.org, DOGnzb.cr, \ Spotweb, NZBGeek, \ - SmackDown, NZBFinder', + NZBFinder', 'wizard': True, 'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAACVBMVEVjhwD///86aRovd/sBAAAAMklEQVQI12NgAIPQUCCRmQkjssDEShiRuRIqwZqZGcDAGBrqANUhGgIkWAOABKMDxCAA24UK50b26SAAAAAASUVORK5CYII=', 'options': [ @@ -231,30 +231,30 @@ config = [{ }, { 'name': 'use', - 'default': '0,0,0,0,0,0' + 'default': '0,0,0,0,0' }, { 'name': 'host', - 'default': 'api.nzb.su,api.dognzb.cr,nzbs.org,https://index.nzbgeek.info, https://smackdownonyou.com, https://www.nzbfinder.ws', + 'default': 'api.nzb.su,api.dognzb.cr,nzbs.org,https://api.nzbgeek.info,https://www.nzbfinder.ws', 'description': 'The hostname of your newznab provider', }, { 'name': 'extra_score', 'advanced': True, 'label': 'Extra Score', - 'default': '0,0,0,0,0,0', + 'default': '0,0,0,0,0', 'description': 'Starting score for each release found via this provider.', }, { 'name': 'custom_tag', 'advanced': True, 'label': 'Custom tag', - 'default': ',,,,,', + 'default': ',,,,', 'description': 'Add custom tags, for example add rls=1 to get only scene releases from nzbs.org', }, { 'name': 'api_key', - 'default': ',,,,,', + 'default': ',,,,', 'label': 'Api Key', 'description': 'Can be found on your profile page', 'type': 'combined', diff --git a/couchpotato/core/media/_base/providers/torrent/awesomehd.py b/couchpotato/core/media/_base/providers/torrent/awesomehd.py index 78c46488..bd9e1932 100644 --- a/couchpotato/core/media/_base/providers/torrent/awesomehd.py +++ b/couchpotato/core/media/_base/providers/torrent/awesomehd.py @@ -61,7 +61,7 @@ class Base(TorrentProvider): 'name': re.sub('[^A-Za-z0-9\-_ \(\).]+', '', '%s (%s) %s' % (name, year, torrent_desc)), 'url': self.urls['download'] % (torrent_id, authkey, self.conf('passkey')), 'detail_url': self.urls['detail'] % torrent_id, - 'size': self.parseSize(entry.find('size').get_text()), + 'size': tryInt(entry.find('size').get_text()) / 1048576, 'seeders': tryInt(entry.find('seeders').get_text()), 'leechers': tryInt(entry.find('leechers').get_text()), 'score': torrentscore diff --git a/couchpotato/core/media/_base/providers/torrent/torrentshack.py b/couchpotato/core/media/_base/providers/torrent/torrentshack.py index 1af7e552..226993cc 100644 --- a/couchpotato/core/media/_base/providers/torrent/torrentshack.py +++ b/couchpotato/core/media/_base/providers/torrent/torrentshack.py @@ -13,12 +13,12 @@ log = CPLog(__name__) class Base(TorrentProvider): urls = { - '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?action=advanced&searchstr=%s&scene=%s&filter_cat[%d]=1', - 'download': 'https://torrentshack.net/%s', + 'test': 'http://torrentshack.eu/', + 'login': 'http://torrentshack.eu/login.php', + 'login_check': 'http://torrentshack.eu/inbox.php', + 'detail': 'http://torrentshack.eu/torrent/%s', + 'search': 'http://torrentshack.eu/torrents.php?action=advanced&searchstr=%s&scene=%s&filter_cat[%d]=1', + 'download': 'http://torrentshack.eu/%s', } http_time_between_calls = 1 # Seconds @@ -80,7 +80,7 @@ config = [{ 'tab': 'searcher', 'list': 'torrent_providers', 'name': 'TorrentShack', - 'description': 'TorrentShack', + 'description': 'TorrentShack', 'wizard': True, 'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABmElEQVQoFQXBzY2cVRiE0afqvd84CQiAnxWWtyxsS6ThINBYg2Dc7mZBMEjE4mzs6e9WcY5+ePNuVFJJodQAoLo+SaWCy9rcV8cmjah3CI6iYu7oRU30kE5xxELRfamklY3k1NL19sSm7vPzP/ZdNZzKVDaY2sPZJBh9fv5ITrmG2+Vp4e1sPchVqTCQZJnVXi+/L4uuAJGly1+Pw8CprLbi8Om7tbT19/XRqJUk11JP9uHj9ulxhXbvJbI9qJvr5YkGXFG2IBT8tXczt+sfzDZCp3765f3t9tHEHGEDACma77+8o4oATKk+/PfW9YmHruRFjWoVSFsVsGu1YSKq6Oc37+n98unPZSRlY7vsKDqN+92X3yR9+PdXee3iJNKMStqdcZqoTJbUSi5JOkpfRlhSI0mSpEmCFKoU7FqSNOLAk54uGwCStMUCgLrVic62g7oDoFmmdI+P3S0pDe1xvDqb6XrZqbtzShWNoh9fv/XQHaDdM9OqrZi2M7M3UrB2vlkPS1IbdEBk7UiSoD6VlZ6aKWer4aH4f/AvKoHUTjuyAAAAAElFTkSuQmCC', 'options': [ diff --git a/couchpotato/core/media/movie/providers/torrent/torrentleech.py b/couchpotato/core/media/movie/providers/torrent/torrentleech.py index 191ceba8..d72f4257 100644 --- a/couchpotato/core/media/movie/providers/torrent/torrentleech.py +++ b/couchpotato/core/media/movie/providers/torrent/torrentleech.py @@ -11,7 +11,7 @@ autoload = 'TorrentLeech' class TorrentLeech(MovieProvider, Base): cat_ids = [ - ([13], ['720p', '1080p']), + ([13], ['720p', '1080p', 'bd50']), ([8], ['cam']), ([9], ['ts', 'tc']), ([10], ['r5', 'scr']), diff --git a/couchpotato/core/media/movie/searcher.py b/couchpotato/core/media/movie/searcher.py index 7d92c57e..4bd8c8d7 100644 --- a/couchpotato/core/media/movie/searcher.py +++ b/couchpotato/core/media/movie/searcher.py @@ -240,7 +240,7 @@ class MovieSearcher(SearcherBase, MovieTypeBase): break if total_result_count > 0: - fireEvent('media.tag', movie['_id'], 'recent', single = True) + fireEvent('media.tag', movie['_id'], 'recent', update_edited = True, single = True) if len(too_early_to_search) > 0: log.info2('Too early to search for %s, %s', (too_early_to_search, default_title)) diff --git a/couchpotato/core/notifications/pushbullet.py b/couchpotato/core/notifications/pushbullet.py index 361294e4..e9d4605c 100644 --- a/couchpotato/core/notifications/pushbullet.py +++ b/couchpotato/core/notifications/pushbullet.py @@ -84,7 +84,8 @@ config = [{ }, { 'name': 'api_key', - 'label': 'User API Key' + 'label': 'Access Token', + 'description': 'Can be found on Account Settings', }, { 'name': 'devices', diff --git a/couchpotato/core/notifications/pushover.py b/couchpotato/core/notifications/pushover.py index d9ef226c..46dc0ad8 100644 --- a/couchpotato/core/notifications/pushover.py +++ b/couchpotato/core/notifications/pushover.py @@ -1,7 +1,7 @@ from httplib import HTTPSConnection from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode -from couchpotato.core.helpers.variable import getTitle +from couchpotato.core.helpers.variable import getTitle, getIdentifier from couchpotato.core.logger import CPLog from couchpotato.core.notifications.base import Notification @@ -27,9 +27,9 @@ class Pushover(Notification): 'sound': self.conf('sound'), } - if data and data.get('identifier'): + if data and getIdentifier(data): api_data.update({ - 'url': toUnicode('http://www.imdb.com/title/%s/' % data['identifier']), + 'url': toUnicode('http://www.imdb.com/title/%s/' % getIdentifier(data)), 'url_title': toUnicode('%s on IMDb' % getTitle(data)), }) diff --git a/couchpotato/core/notifications/xbmc.py b/couchpotato/core/notifications/xbmc.py index eb0b6996..bf5310e0 100644 --- a/couchpotato/core/notifications/xbmc.py +++ b/couchpotato/core/notifications/xbmc.py @@ -208,7 +208,7 @@ class XBMC(Notification): log.debug('Returned from request %s: %s', (host, response)) return response - except (MaxRetryError, requests.exceptions.Timeout): + except (MaxRetryError, requests.exceptions.Timeout, ConnectionError): log.info2('Couldn\'t send request to XBMC, assuming it\'s turned off') return [] except: diff --git a/couchpotato/core/plugins/base.py b/couchpotato/core/plugins/base.py index bc66123f..2c7f9e13 100644 --- a/couchpotato/core/plugins/base.py +++ b/couchpotato/core/plugins/base.py @@ -157,7 +157,7 @@ class Plugin(object): os.rmdir(full_path) except: if show_error: - log.error('Couldn\'t remove empty directory %s: %s', (full_path, traceback.format_exc())) + log.info2('Couldn\'t remove directory %s: %s', (full_path, traceback.format_exc())) try: os.rmdir(folder) diff --git a/couchpotato/core/plugins/log/static/log.js b/couchpotato/core/plugins/log/static/log.js index 11acb5ca..6cb683bc 100644 --- a/couchpotato/core/plugins/log/static/log.js +++ b/couchpotato/core/plugins/log/static/log.js @@ -241,7 +241,7 @@ Running on: ...\n\ 'href': 'https://github.com/RuudBurger/CouchPotatoServer/blob/develop/contributing.md' }), new Element('span', { - 'text': ' before posting, then copy the text below' + 'text': ' before posting (kittens die if you don\'t), then copy the text below.' }) ), textarea = new Element('textarea', { diff --git a/couchpotato/core/plugins/quality/main.py b/couchpotato/core/plugins/quality/main.py index bc61afa2..2687cb14 100644 --- a/couchpotato/core/plugins/quality/main.py +++ b/couchpotato/core/plugins/quality/main.py @@ -29,9 +29,9 @@ class QualityPlugin(Plugin): {'identifier': 'dvdr', 'size': (3000, 10000), 'label': 'DVD-R', 'alternative': ['br2dvd', ('dvd', 'r')], 'allow': [], 'ext':['iso', 'img', 'vob'], 'tags': ['pal', 'ntsc', 'video_ts', 'audio_ts', ('dvd', 'r'), 'dvd9']}, {'identifier': 'dvdrip', 'size': (600, 2400), 'label': 'DVD-Rip', 'width': 720, 'alternative': [('dvd', 'rip')], 'allow': [], 'ext':['avi'], 'tags': [('dvd', 'rip'), ('dvd', 'xvid'), ('dvd', 'divx')]}, {'identifier': 'scr', 'size': (600, 1600), 'label': 'Screener', 'alternative': ['screener', 'dvdscr', 'ppvrip', 'dvdscreener', 'hdscr'], 'allow': ['dvdr', 'dvdrip', '720p', '1080p'], 'ext':[], 'tags': ['webrip', ('web', 'rip')]}, - {'identifier': 'r5', 'size': (600, 1000), 'label': 'R5', 'alternative': ['r6'], 'allow': ['dvdr'], 'ext':[]}, - {'identifier': 'tc', 'size': (600, 1000), 'label': 'TeleCine', 'alternative': ['telecine'], 'allow': [], 'ext':[]}, - {'identifier': 'ts', 'size': (600, 1000), 'label': 'TeleSync', 'alternative': ['telesync', 'hdts'], 'allow': [], 'ext':[]}, + {'identifier': 'r5', 'size': (600, 1000), 'label': 'R5', 'alternative': ['r6'], 'allow': ['dvdr', '720p'], 'ext':[]}, + {'identifier': 'tc', 'size': (600, 1000), 'label': 'TeleCine', 'alternative': ['telecine'], 'allow': ['720p'], 'ext':[]}, + {'identifier': 'ts', 'size': (600, 1000), 'label': 'TeleSync', 'alternative': ['telesync', 'hdts'], 'allow': ['720p'], 'ext':[]}, {'identifier': 'cam', 'size': (600, 1000), 'label': 'Cam', 'alternative': ['camrip', 'hdcam'], 'allow': ['720p'], 'ext':[]} ] pre_releases = ['cam', 'ts', 'tc', 'r5', 'scr'] @@ -454,6 +454,9 @@ class QualityPlugin(Plugin): 'Moviename 2014 720p HDCAM XviD DualAudio': {'size': 4000, 'quality': 'cam'}, 'Moviename (2014) - 720p CAM x264': {'size': 2250, 'quality': 'cam'}, 'Movie Name (2014).mp4': {'size': 750, 'quality': 'brrip'}, + 'Moviename.2014.720p.R6.WEB-DL.x264.AC3-xyz': {'size': 750, 'quality': 'r5'}, + 'Movie name 2014 New Source 720p HDCAM x264 AC3 xyz': {'size': 750, 'quality': 'cam'}, + 'Movie.Name.2014.720p.HD.TS.AC3.x264': {'size': 750, 'quality': 'ts'} } correct = 0 diff --git a/couchpotato/core/plugins/release/main.py b/couchpotato/core/plugins/release/main.py index 196892ce..b1ecafd8 100644 --- a/couchpotato/core/plugins/release/main.py +++ b/couchpotato/core/plugins/release/main.py @@ -234,8 +234,9 @@ class Release(Plugin): db = get_db() try: - rel = db.get('id', id, with_doc = True) - self.updateStatus(id, 'available' if rel['status'] in ['ignored', 'failed'] else 'ignored') + if id: + rel = db.get('id', id, with_doc = True) + self.updateStatus(id, 'available' if rel['status'] in ['ignored', 'failed'] else 'ignored') return { 'success': True @@ -327,7 +328,7 @@ class Release(Plugin): log_movie = '%s (%s) in %s' % (getTitle(media), media['info']['year'], rls['quality']) snatch_message = 'Snatched "%s": %s' % (data.get('name'), log_movie) log.info(snatch_message) - fireEvent('%s.snatched' % data['type'], message = snatch_message, data = rls) + fireEvent('%s.snatched' % data['type'], message = snatch_message, data = media) # Mark release as snatched if renamer_enabled: @@ -364,7 +365,7 @@ class Release(Plugin): let_through = False filtered_results = [] - # If a single release comes through the "wait for", let through all + # Filter out ignored and other releases we don't want for rel in results: if rel['status'] in ['ignored', 'failed']: @@ -379,6 +380,11 @@ class Release(Plugin): log.info('Ignored, size "%sMB" to low: %s', (rel['size'], rel['name'])) continue + if 'seeders' in rel and rel.get('seeders') <= 0: + log.info('Ignored, no seeders: %s', (rel['name'])) + continue + + # If a single release comes through the "wait for", let through all rel['wait_for'] = False if quality_custom.get('index') != 0 and quality_custom.get('wait_for', 0) > 0 and rel.get('age') <= quality_custom.get('wait_for', 0): rel['wait_for'] = True diff --git a/couchpotato/core/plugins/renamer.py b/couchpotato/core/plugins/renamer.py index 481c9dd8..ad72086e 100644 --- a/couchpotato/core/plugins/renamer.py +++ b/couchpotato/core/plugins/renamer.py @@ -123,11 +123,6 @@ class Renamer(Plugin): no_process = [to_folder] cat_list = fireEvent('category.all', single = True) or [] no_process.extend([item['destination'] for item in cat_list]) - try: - if Env.setting('library', section = 'manage').strip(): - no_process.extend([sp(manage_folder) for manage_folder in splitString(Env.setting('library', section = 'manage'), '::')]) - except: - pass # Check to see if the no_process folders are inside the "from" folder. if not os.path.isdir(base_folder) or not os.path.isdir(to_folder): @@ -202,14 +197,18 @@ class Renamer(Plugin): db = get_db() # Extend the download info with info stored in the downloaded release + keep_original = self.moveTypeIsLinked() + is_torrent = False if release_download: release_download = self.extendReleaseDownload(release_download) + is_torrent = self.downloadIsTorrent(release_download) + keep_original = True if is_torrent and self.conf('file_action') not in ['move'] else keep_original # Unpack any archives extr_files = None if self.conf('unrar'): folder, media_folder, files, extr_files = self.extractFiles(folder = folder, media_folder = media_folder, files = files, - cleanup = self.conf('cleanup') and not self.downloadIsTorrent(release_download)) + cleanup = self.conf('cleanup') and not keep_original) groups = fireEvent('scanner.scan', folder = folder if folder else base_folder, files = files, release_download = release_download, return_ignored = False, single = True) or [] @@ -326,7 +325,7 @@ class Renamer(Plugin): if file_type is 'nfo' and not self.conf('rename_nfo'): log.debug('Skipping, renaming of %s disabled', file_type) for current_file in group['files'][file_type]: - if self.conf('cleanup') and (not self.downloadIsTorrent(release_download) or self.fileIsAdded(current_file, group)): + if self.conf('cleanup') and (not keep_original or self.fileIsAdded(current_file, group)): remove_files.append(current_file) continue @@ -523,7 +522,7 @@ class Renamer(Plugin): # Mark media for dashboard if mark_as_recent: - fireEvent('media.tag', group['media'].get('_id'), 'recent', single = True) + fireEvent('media.tag', group['media'].get('_id'), 'recent', update_edited = True, single = True) # Remove leftover files if not remove_leftovers: # Don't remove anything @@ -532,7 +531,7 @@ class Renamer(Plugin): log.debug('Removing leftover files') for current_file in group['files']['leftover']: if self.conf('cleanup') and not self.conf('move_leftover') and \ - (not self.downloadIsTorrent(release_download) or self.fileIsAdded(current_file, group)): + (not keep_original or self.fileIsAdded(current_file, group)): remove_files.append(current_file) # Remove files @@ -579,7 +578,7 @@ class Renamer(Plugin): self.makeDir(os.path.dirname(dst)) try: - self.moveFile(src, dst, forcemove = not self.downloadIsTorrent(release_download) or self.fileIsAdded(src, group)) + self.moveFile(src, dst, use_default = not is_torrent or self.fileIsAdded(src, group)) group['renamed_files'].append(dst) except: log.error('Failed renaming the file "%s" : %s', (os.path.basename(src), traceback.format_exc())) @@ -595,7 +594,7 @@ class Renamer(Plugin): self.untagRelease(group = group, tag = 'failed_rename') # Tag folder if it is in the 'from' folder and it will not be removed because it is a torrent - if self.movieInFromFolder(media_folder) and self.downloadIsTorrent(release_download): + if self.movieInFromFolder(media_folder) and keep_original: self.tagRelease(group = group, tag = 'renamed_already') # Remove matching releases @@ -606,7 +605,7 @@ class Renamer(Plugin): except: log.error('Failed removing %s: %s', (release, traceback.format_exc())) - if group['dirname'] and group['parentdir'] and not self.downloadIsTorrent(release_download): + if group['dirname'] and group['parentdir'] and not keep_original: if media_folder: # Delete the movie folder group_folder = media_folder @@ -768,10 +767,15 @@ Remove it if you want it to be renamed (again, or at least let it try again) return False - def moveFile(self, old, dest, forcemove = False): + def moveFile(self, old, dest, use_default = False): dest = sp(dest) try: - if forcemove or self.conf('file_action') not in ['copy', 'link']: + + move_type = self.conf('file_action') + if use_default: + move_type = self.conf('default_file_action') + + if move_type not in ['copy', 'link']: try: shutil.move(old, dest) except: @@ -780,16 +784,16 @@ Remove it if you want it to be renamed (again, or at least let it try again) os.unlink(old) else: raise - elif self.conf('file_action') == 'copy': + elif move_type == 'copy': shutil.copy(old, dest) - elif self.conf('file_action') == 'link': + else: # First try to hardlink try: log.debug('Hardlinking file "%s" to "%s"...', (old, dest)) link(old, dest) except: # Try to simlink next - log.debug('Couldn\'t hardlink file "%s" to "%s". Simlinking instead. Error: %s.', (old, dest, traceback.format_exc())) + log.debug('Couldn\'t hardlink file "%s" to "%s". Symlinking instead. Error: %s.', (old, dest, traceback.format_exc())) shutil.copy(old, dest) try: symlink(dest, old + '.link') @@ -803,7 +807,7 @@ Remove it if you want it to be renamed (again, or at least let it try again) if os.name == 'nt' and self.conf('ntfs_permission'): os.popen('icacls "' + dest + '"* /reset /T') except: - log.error('Failed setting permissions for file: %s, %s', (dest, traceback.format_exc(1))) + log.debug('Failed setting permissions for file: %s, %s', (dest, traceback.format_exc(1))) except: log.error('Couldn\'t move file "%s" to "%s": %s', (old, dest, traceback.format_exc())) raise @@ -1089,6 +1093,9 @@ Remove it if you want it to be renamed (again, or at least let it try again) return False return src in group['before_rename'] + def moveTypeIsLinked(self): + return self.conf('default_file_action') in ['copy', 'link'] + def statusInfoComplete(self, release_download): return release_download.get('id') and release_download.get('downloader') and release_download.get('folder') @@ -1140,14 +1147,20 @@ Remove it if you want it to be renamed (again, or at least let it try again) log.info('Archive %s found. Extracting...', os.path.basename(archive['file'])) try: - rar_handle = RarFile(archive['file']) + rar_handle = RarFile(archive['file'], custom_path = self.conf('unrar_path')) extr_path = os.path.join(from_folder, os.path.relpath(os.path.dirname(archive['file']), folder)) self.makeDir(extr_path) for packedinfo in rar_handle.infolist(): - if not packedinfo.isdir and not os.path.isfile(sp(os.path.join(extr_path, os.path.basename(packedinfo.filename)))): + extr_file_path = sp(os.path.join(extr_path, os.path.basename(packedinfo.filename))) + if not packedinfo.isdir and not os.path.isfile(extr_file_path): log.debug('Extracting %s...', packedinfo.filename) rar_handle.extract(condition = [packedinfo.index], path = extr_path, withSubpath = False, overwrite = False) - extr_files.append(sp(os.path.join(extr_path, os.path.basename(packedinfo.filename)))) + if self.conf('unrar_modify_date'): + try: + os.utime(extr_file_path, (os.path.getatime(archive['file']), os.path.getmtime(archive['file']))) + except: + log.error('Rar modify date enabled, but failed: %s', traceback.format_exc()) + extr_files.append(extr_file_path) del rar_handle except Exception as e: log.error('Failed to extract %s: %s %s', (archive['file'], e, traceback.format_exc())) @@ -1282,6 +1295,18 @@ config = [{ 'description': 'Extract rar files if found.', 'default': False, }, + { + 'advanced': True, + 'name': 'unrar_path', + 'description': 'Custom path to unrar bin', + }, + { + 'advanced': True, + 'name': 'unrar_modify_date', + 'type': 'bool', + 'description': ('Set modify date of unrar-ed files to the rar-file\'s date.', 'This will allow XBMC to recognize extracted files as recently added even if the movie was released some time ago.'), + 'default': False, + }, { 'name': 'cleanup', 'type': 'bool', @@ -1332,14 +1357,23 @@ config = [{ 'label': 'Folder-Separator', 'description': ('Replace all the spaces with a character.', 'Example: ".", "-" (without quotes). Leave empty to use spaces.'), }, + { + 'name': 'default_file_action', + 'label': 'Default File Action', + 'default': 'move', + 'type': 'dropdown', + 'values': [('Link', 'link'), ('Copy', 'copy'), ('Move', 'move')], + 'description': ('Link, Copy or Move after download completed.', + 'Link first tries hard link, then sym link and falls back to Copy.'), + 'advanced': True, + }, { 'name': 'file_action', 'label': 'Torrent File Action', 'default': 'link', 'type': 'dropdown', 'values': [('Link', 'link'), ('Copy', 'copy'), ('Move', 'move')], - 'description': ('Link, Copy or Move after download completed.', - 'Link first tries hard link, then sym link and falls back to Copy. It is perfered to use link when downloading torrents as it will save you space, while still beeing able to seed.'), + 'description': 'See above. It is prefered to use link when downloading torrents as it will save you space, while still beeing able to seed.', 'advanced': True, }, { diff --git a/couchpotato/core/plugins/scanner.py b/couchpotato/core/plugins/scanner.py index 6a4d537d..3d39b290 100644 --- a/couchpotato/core/plugins/scanner.py +++ b/couchpotato/core/plugins/scanner.py @@ -553,7 +553,7 @@ class Scanner(Plugin): scan_result = [] for p in paths: if not group['is_dvd']: - video = Video.from_path(toUnicode(p)) + video = Video.from_path(sp(p)) video_result = [(video, video.scan())] scan_result.extend(video_result) diff --git a/couchpotato/templates/index.html b/couchpotato/templates/index.html index 0d8acbc1..729f1e3d 100644 --- a/couchpotato/templates/index.html +++ b/couchpotato/templates/index.html @@ -5,7 +5,7 @@ - + {% for url in fireEvent('clientscript.get_styles', as_html = True, location = 'front', single = True) %} {% end %} diff --git a/couchpotato/templates/login.html b/couchpotato/templates/login.html index 35626221..e33db2d7 100644 --- a/couchpotato/templates/login.html +++ b/couchpotato/templates/login.html @@ -4,22 +4,24 @@
+ + {% for url in fireEvent('clientscript.get_styles', as_html = True, location = 'front', single = True) %} {% end %} - + {% for url in fireEvent('clientscript.get_scripts', as_html = True, location = 'front', single = True) %} {% end %} - +