Merge branch 'refs/heads/develop' into desktop
Conflicts: couchpotato/core/_base/updater/main.py
This commit is contained in:
@@ -53,7 +53,7 @@ class Core(Plugin):
|
|||||||
|
|
||||||
|
|
||||||
def md5Password(self, value):
|
def md5Password(self, value):
|
||||||
return md5(value) if value else ''
|
return md5(value.encode(Env.get('encoding'))) if value else ''
|
||||||
|
|
||||||
def checkApikey(self, value):
|
def checkApikey(self, value):
|
||||||
return value if value and len(value) > 3 else uuid4().hex
|
return value if value and len(value) > 3 else uuid4().hex
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import shutil
|
|||||||
import tarfile
|
import tarfile
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
|
import version
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
@@ -102,6 +103,8 @@ class Updater(Plugin):
|
|||||||
success = False
|
success = False
|
||||||
else:
|
else:
|
||||||
success = self.updater.doUpdate()
|
success = self.updater.doUpdate()
|
||||||
|
if success:
|
||||||
|
fireEventAsync('app.restart')
|
||||||
|
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': success
|
'success': success
|
||||||
@@ -112,7 +115,7 @@ class BaseUpdater(Plugin):
|
|||||||
|
|
||||||
repo_user = 'RuudBurger'
|
repo_user = 'RuudBurger'
|
||||||
repo_name = 'CouchPotatoServer'
|
repo_name = 'CouchPotatoServer'
|
||||||
branch = 'desktop'
|
branch = version.BRANCH
|
||||||
|
|
||||||
version = None
|
version = None
|
||||||
update_failed = False
|
update_failed = False
|
||||||
|
|||||||
@@ -17,8 +17,9 @@ def requires_auth(f):
|
|||||||
@wraps(f)
|
@wraps(f)
|
||||||
def decorated(*args, **kwargs):
|
def decorated(*args, **kwargs):
|
||||||
auth = getattr(request, 'authorization')
|
auth = getattr(request, 'authorization')
|
||||||
if Env.setting('username') and Env.setting('password') and (not auth or not check_auth(auth.username, md5(auth.password))):
|
if Env.setting('username') and Env.setting('password'):
|
||||||
return authenticate()
|
if (not auth or not check_auth(auth.username.decode('latin1'), md5(auth.password.decode('latin1').encode(Env.get('encoding'))))):
|
||||||
|
return authenticate()
|
||||||
|
|
||||||
return f(*args, **kwargs)
|
return f(*args, **kwargs)
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from couchpotato.core.helpers.encoding import isInt
|
|||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
import httplib
|
import httplib
|
||||||
import json
|
import json
|
||||||
|
import os.path
|
||||||
import re
|
import re
|
||||||
import urllib2
|
import urllib2
|
||||||
|
|
||||||
@@ -31,7 +32,7 @@ class Transmission(Downloader):
|
|||||||
# Set parameters for Transmission
|
# Set parameters for Transmission
|
||||||
params = {
|
params = {
|
||||||
'paused': self.conf('paused', default = 0),
|
'paused': self.conf('paused', default = 0),
|
||||||
'download-dir': self.conf('directory', default = None)
|
'download-dir': self.conf('directory', default = '').rstrip(os.path.sep)
|
||||||
}
|
}
|
||||||
|
|
||||||
torrent_params = {
|
torrent_params = {
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ from couchpotato.core.helpers.encoding import toUnicode
|
|||||||
from couchpotato.core.helpers.variable import natcmp
|
from couchpotato.core.helpers.variable import natcmp
|
||||||
from flask.globals import current_app
|
from flask.globals import current_app
|
||||||
from flask.helpers import json, make_response
|
from flask.helpers import json, make_response
|
||||||
from libs.werkzeug.urls import url_decode
|
|
||||||
from urllib import unquote
|
from urllib import unquote
|
||||||
|
from werkzeug.urls import url_decode
|
||||||
import flask
|
import flask
|
||||||
import re
|
import re
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ def dictToList(params):
|
|||||||
|
|
||||||
def getParam(attr, default = None):
|
def getParam(attr, default = None):
|
||||||
try:
|
try:
|
||||||
return toUnicode(unquote(getattr(flask.request, 'args').get(attr, default)))
|
return getParams().get(attr, default)
|
||||||
except:
|
except:
|
||||||
return default
|
return default
|
||||||
|
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ def tryInt(s):
|
|||||||
|
|
||||||
def tryFloat(s):
|
def tryFloat(s):
|
||||||
try: return float(s) if '.' in s else tryInt(s)
|
try: return float(s) if '.' in s else tryInt(s)
|
||||||
except: return s
|
except: return 0
|
||||||
|
|
||||||
def natsortKey(s):
|
def natsortKey(s):
|
||||||
return map(tryInt, re.findall(r'(\d+|\D+)', s))
|
return map(tryInt, re.findall(r'(\d+|\D+)', s))
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ config = [{
|
|||||||
{
|
{
|
||||||
'tab': 'notifications',
|
'tab': 'notifications',
|
||||||
'name': 'growl',
|
'name': 'growl',
|
||||||
|
'description': 'Version 1.4+',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
from couchpotato.core.event import addEvent
|
from couchpotato.core.event import addEvent
|
||||||
from couchpotato.core.helpers.encoding import tryUrlencode
|
from couchpotato.core.helpers.encoding import tryUrlencode
|
||||||
|
from couchpotato.core.helpers.request import jsonified
|
||||||
from couchpotato.core.helpers.variable import cleanHost
|
from couchpotato.core.helpers.variable import cleanHost
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.notifications.base import Notification
|
from couchpotato.core.notifications.base import Notification
|
||||||
|
from urllib2 import URLError
|
||||||
from xml.dom import minidom
|
from xml.dom import minidom
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
@@ -38,7 +40,7 @@ class Plex(Notification):
|
|||||||
x = self.urlopen(url)
|
x = self.urlopen(url)
|
||||||
|
|
||||||
except:
|
except:
|
||||||
log.error('Plex library update failed for %s: %s', (host, traceback.format_exc()))
|
log.error('Plex library update failed for %s, Media Server not running: %s', (host, traceback.format_exc(1)))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@@ -62,9 +64,27 @@ class Plex(Notification):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
self.urlopen(url, headers = headers, show_error = False)
|
self.urlopen(url, headers = headers, show_error = False)
|
||||||
|
except URLError:
|
||||||
|
log.error("Couldn't sent command to Plex, probably just running Media Server")
|
||||||
|
return False
|
||||||
except:
|
except:
|
||||||
log.error("Couldn't sent command to Plex: %s", traceback.format_exc())
|
log.error("Couldn't sent command to Plex: %s", traceback.format_exc())
|
||||||
return False
|
return False
|
||||||
|
|
||||||
log.info('Plex notification to %s successful.', host)
|
log.info('Plex notification to %s successful.', host)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def test(self):
|
||||||
|
|
||||||
|
test_type = self.testNotifyName()
|
||||||
|
|
||||||
|
log.info('Sending test to %s', test_type)
|
||||||
|
|
||||||
|
success = self.notify(
|
||||||
|
message = self.test_message,
|
||||||
|
data = {},
|
||||||
|
listener = 'test'
|
||||||
|
)
|
||||||
|
success2 = self.addToLibrary()
|
||||||
|
|
||||||
|
return jsonified({'success': success or success2})
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from StringIO import StringIO
|
||||||
from couchpotato import addView
|
from couchpotato import addView
|
||||||
from couchpotato.core.event import fireEvent, addEvent
|
from couchpotato.core.event import fireEvent, addEvent
|
||||||
from couchpotato.core.helpers.encoding import tryUrlencode, simplifyString, ss
|
from couchpotato.core.helpers.encoding import tryUrlencode, simplifyString, ss
|
||||||
@@ -9,6 +10,7 @@ from multipartpost import MultipartPostHandler
|
|||||||
from urlparse import urlparse
|
from urlparse import urlparse
|
||||||
import cookielib
|
import cookielib
|
||||||
import glob
|
import glob
|
||||||
|
import gzip
|
||||||
import math
|
import math
|
||||||
import os.path
|
import os.path
|
||||||
import re
|
import re
|
||||||
@@ -101,10 +103,10 @@ class Plugin(object):
|
|||||||
if not params: params = {}
|
if not params: params = {}
|
||||||
|
|
||||||
# Fill in some headers
|
# Fill in some headers
|
||||||
if not headers.get('Referer'):
|
headers['Referer'] = headers.get('Referer', urlparse(url).hostname)
|
||||||
headers['Referer'] = urlparse(url).hostname
|
headers['Host'] = headers.get('Host', urlparse(url).hostname)
|
||||||
if not headers.get('User-Agent'):
|
headers['User-Agent'] = headers.get('User-Agent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:10.0.2) Gecko/20100101 Firefox/10.0.2')
|
||||||
headers['User-Agent'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:10.0.2) Gecko/20100101 Firefox/10.0.2'
|
headers['Accept-encoding'] = headers.get('Accept-encoding', 'gzip')
|
||||||
|
|
||||||
host = urlparse(url).hostname
|
host = urlparse(url).hostname
|
||||||
|
|
||||||
@@ -127,16 +129,24 @@ class Plugin(object):
|
|||||||
cookies = cookielib.CookieJar()
|
cookies = cookielib.CookieJar()
|
||||||
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookies), MultipartPostHandler)
|
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookies), MultipartPostHandler)
|
||||||
|
|
||||||
data = opener.open(request, timeout = timeout).read()
|
response = opener.open(request, timeout = timeout)
|
||||||
else:
|
else:
|
||||||
log.info('Opening url: %s, params: %s', (url, [x for x in params.iterkeys()]))
|
log.info('Opening url: %s, params: %s', (url, [x for x in params.iterkeys()]))
|
||||||
data = tryUrlencode(params) if len(params) > 0 else None
|
data = tryUrlencode(params) if len(params) > 0 else None
|
||||||
request = urllib2.Request(url, data, headers)
|
request = urllib2.Request(url, data, headers)
|
||||||
|
|
||||||
if opener:
|
if opener:
|
||||||
data = opener.open(request, timeout = timeout).read()
|
response = opener.open(request, timeout = timeout)
|
||||||
else:
|
else:
|
||||||
data = urllib2.urlopen(request, timeout = timeout).read()
|
response = urllib2.urlopen(request, timeout = timeout)
|
||||||
|
|
||||||
|
# unzip if needed
|
||||||
|
if response.info().get('Content-Encoding') == 'gzip':
|
||||||
|
buf = StringIO(response.read())
|
||||||
|
f = gzip.GzipFile(fileobj = buf)
|
||||||
|
data = f.read()
|
||||||
|
else:
|
||||||
|
data = response.read()
|
||||||
|
|
||||||
self.http_failed_request[host] = 0
|
self.http_failed_request[host] = 0
|
||||||
except IOError:
|
except IOError:
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import fireEvent, addEvent, fireEventAsync
|
from couchpotato.core.event import fireEvent, addEvent, fireEventAsync
|
||||||
|
from couchpotato.core.helpers.encoding import ss
|
||||||
from couchpotato.core.helpers.request import jsonified, getParam
|
from couchpotato.core.helpers.request import jsonified, getParam
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
@@ -77,7 +78,7 @@ class Manage(Plugin):
|
|||||||
for release in done_movie.get('releases', []):
|
for release in done_movie.get('releases', []):
|
||||||
for release_file in release.get('files', []):
|
for release_file in release.get('files', []):
|
||||||
# Remove release not available anymore
|
# Remove release not available anymore
|
||||||
if not os.path.isfile(release_file['path']):
|
if not os.path.isfile(ss(release_file['path'])):
|
||||||
fireEvent('release.clean', release['id'])
|
fireEvent('release.clean', release['id'])
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|||||||
@@ -370,7 +370,7 @@ var ReleaseAction = new Class({
|
|||||||
}).adopt(
|
}).adopt(
|
||||||
new Element('span.name', {'text': self.get(release, 'name'), 'title': self.get(release, 'name')}),
|
new Element('span.name', {'text': self.get(release, 'name'), 'title': self.get(release, 'name')}),
|
||||||
new Element('span.status', {'text': status.identifier, 'class': 'release_status '+status.identifier}),
|
new Element('span.status', {'text': status.identifier, 'class': 'release_status '+status.identifier}),
|
||||||
new Element('span.quality', {'text': quality.get('label')}),
|
new Element('span.quality', {'text': quality.label || 'n/a'}),
|
||||||
new Element('span.size', {'text': (self.get(release, 'size'))}),
|
new Element('span.size', {'text': (self.get(release, 'size'))}),
|
||||||
new Element('span.age', {'text': self.get(release, 'age')}),
|
new Element('span.age', {'text': self.get(release, 'age')}),
|
||||||
new Element('span.score', {'text': self.get(release, 'score')}),
|
new Element('span.score', {'text': self.get(release, 'score')}),
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from couchpotato import get_session
|
from couchpotato import get_session
|
||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import fireEvent, addEvent
|
from couchpotato.core.event import fireEvent, addEvent
|
||||||
|
from couchpotato.core.helpers.encoding import ss
|
||||||
from couchpotato.core.helpers.request import getParam, jsonified
|
from couchpotato.core.helpers.request import getParam, jsonified
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
@@ -131,7 +132,7 @@ class Release(Plugin):
|
|||||||
rel = db.query(Relea).filter_by(id = id).first()
|
rel = db.query(Relea).filter_by(id = id).first()
|
||||||
if rel:
|
if rel:
|
||||||
for release_file in rel.files:
|
for release_file in rel.files:
|
||||||
if not os.path.isfile(release_file.path):
|
if not os.path.isfile(ss(release_file.path)):
|
||||||
db.delete(release_file)
|
db.delete(release_file)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from couchpotato import get_session
|
from couchpotato import get_session
|
||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import addEvent, fireEvent, fireEventAsync
|
from couchpotato.core.event import addEvent, fireEvent, fireEventAsync
|
||||||
from couchpotato.core.helpers.encoding import toUnicode
|
from couchpotato.core.helpers.encoding import toUnicode, ss
|
||||||
from couchpotato.core.helpers.request import jsonified
|
from couchpotato.core.helpers.request import jsonified
|
||||||
from couchpotato.core.helpers.variable import getExt, mergeDicts, getTitle
|
from couchpotato.core.helpers.variable import getExt, mergeDicts, getTitle
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
@@ -12,6 +12,7 @@ import os
|
|||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import traceback
|
import traceback
|
||||||
|
import errno
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
@@ -419,6 +420,7 @@ class Renamer(Plugin):
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
def moveFile(self, old, dest):
|
def moveFile(self, old, dest):
|
||||||
|
dest = ss(dest)
|
||||||
try:
|
try:
|
||||||
shutil.move(old, dest)
|
shutil.move(old, dest)
|
||||||
|
|
||||||
@@ -427,6 +429,14 @@ class Renamer(Plugin):
|
|||||||
except:
|
except:
|
||||||
log.error('Failed setting permissions for file: %s, %s', (dest, traceback.format_exc(1)))
|
log.error('Failed setting permissions for file: %s, %s', (dest, traceback.format_exc(1)))
|
||||||
|
|
||||||
|
except OSError, err:
|
||||||
|
# Copying from a filesystem with octal permission to an NTFS file system causes a permission error. In this case ignore it.
|
||||||
|
if not hasattr(os, 'chmod') or err.errno != errno.EPERM:
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
if os.path.exists(dest):
|
||||||
|
os.unlink(old)
|
||||||
|
|
||||||
except:
|
except:
|
||||||
log.error('Couldn\'t move file "%s" to "%s": %s', (old, dest, traceback.format_exc()))
|
log.error('Couldn\'t move file "%s" to "%s": %s', (old, dest, traceback.format_exc()))
|
||||||
raise Exception
|
raise Exception
|
||||||
|
|||||||
@@ -74,8 +74,6 @@ class Scanner(Plugin):
|
|||||||
|
|
||||||
cp_imdb = '(\.cp\((?P<id>tt[0-9{7}]+)\))'
|
cp_imdb = '(\.cp\((?P<id>tt[0-9{7}]+)\))'
|
||||||
|
|
||||||
path_identifiers = {} # bind identifier to filepath
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
addEvent('scanner.create_file_identifier', self.createStringIdentifier)
|
addEvent('scanner.create_file_identifier', self.createStringIdentifier)
|
||||||
@@ -222,18 +220,19 @@ class Scanner(Plugin):
|
|||||||
|
|
||||||
|
|
||||||
# Create identifiers for all leftover files
|
# Create identifiers for all leftover files
|
||||||
|
path_identifiers = {}
|
||||||
for file_path in leftovers:
|
for file_path in leftovers:
|
||||||
identifier = self.createStringIdentifier(file_path, folder)
|
identifier = self.createStringIdentifier(file_path, folder)
|
||||||
|
|
||||||
if not self.path_identifiers.get(identifier):
|
if not path_identifiers.get(identifier):
|
||||||
self.path_identifiers[identifier] = []
|
path_identifiers[identifier] = []
|
||||||
|
|
||||||
self.path_identifiers[identifier].append(file_path)
|
path_identifiers[identifier].append(file_path)
|
||||||
|
|
||||||
|
|
||||||
# Group the files based on the identifier
|
# Group the files based on the identifier
|
||||||
delete_identifiers = []
|
delete_identifiers = []
|
||||||
for identifier, found_files in self.path_identifiers.iteritems():
|
for identifier, found_files in path_identifiers.iteritems():
|
||||||
log.debug('Grouping files on identifier: %s', identifier)
|
log.debug('Grouping files on identifier: %s', identifier)
|
||||||
|
|
||||||
group = movie_files.get(identifier)
|
group = movie_files.get(identifier)
|
||||||
@@ -250,13 +249,13 @@ class Scanner(Plugin):
|
|||||||
|
|
||||||
# Cleaning up used
|
# Cleaning up used
|
||||||
for identifier in delete_identifiers:
|
for identifier in delete_identifiers:
|
||||||
if self.path_identifiers.get(identifier):
|
if path_identifiers.get(identifier):
|
||||||
del self.path_identifiers[identifier]
|
del path_identifiers[identifier]
|
||||||
del delete_identifiers
|
del delete_identifiers
|
||||||
|
|
||||||
# Group based on folder
|
# Group based on folder
|
||||||
delete_identifiers = []
|
delete_identifiers = []
|
||||||
for identifier, found_files in self.path_identifiers.iteritems():
|
for identifier, found_files in path_identifiers.iteritems():
|
||||||
log.debug('Grouping files on foldername: %s', identifier)
|
log.debug('Grouping files on foldername: %s', identifier)
|
||||||
|
|
||||||
for ff in found_files:
|
for ff in found_files:
|
||||||
@@ -276,8 +275,8 @@ class Scanner(Plugin):
|
|||||||
|
|
||||||
# Cleaning up used
|
# Cleaning up used
|
||||||
for identifier in delete_identifiers:
|
for identifier in delete_identifiers:
|
||||||
if self.path_identifiers.get(identifier):
|
if path_identifiers.get(identifier):
|
||||||
del self.path_identifiers[identifier]
|
del path_identifiers[identifier]
|
||||||
del delete_identifiers
|
del delete_identifiers
|
||||||
|
|
||||||
# Determine file types
|
# Determine file types
|
||||||
@@ -388,14 +387,11 @@ class Scanner(Plugin):
|
|||||||
|
|
||||||
processed_movies[identifier] = group
|
processed_movies[identifier] = group
|
||||||
|
|
||||||
|
|
||||||
# Clean up
|
|
||||||
self.path_identifiers = {}
|
|
||||||
|
|
||||||
if len(processed_movies) > 0:
|
if len(processed_movies) > 0:
|
||||||
log.info('Found %s movies in the folder %s', (len(processed_movies), folder))
|
log.info('Found %s movies in the folder %s', (len(processed_movies), folder))
|
||||||
else:
|
else:
|
||||||
log.debug('Found no movies in the folder %s', (folder))
|
log.debug('Found no movies in the folder %s', (folder))
|
||||||
|
|
||||||
return processed_movies
|
return processed_movies
|
||||||
|
|
||||||
def getMetaData(self, group):
|
def getMetaData(self, group):
|
||||||
|
|||||||
@@ -429,8 +429,8 @@ class Searcher(Plugin):
|
|||||||
|
|
||||||
if dates.get('dvd') > 0:
|
if dates.get('dvd') > 0:
|
||||||
|
|
||||||
# 3 weeks before dvd release
|
# 4 weeks before dvd release
|
||||||
if dates.get('dvd') - 1814400 < now:
|
if dates.get('dvd') - 2419200 < now:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Dvd should be released
|
# Dvd should be released
|
||||||
|
|||||||
@@ -12,8 +12,7 @@
|
|||||||
|
|
||||||
// ==/UserScript==
|
// ==/UserScript==
|
||||||
|
|
||||||
if (window.top != window.self) // Only run on top window
|
if (window.top == window.self){ // Only run on top window
|
||||||
return;
|
|
||||||
|
|
||||||
var version = {{version}},
|
var version = {{version}},
|
||||||
host = '{{host}}',
|
host = '{{host}}',
|
||||||
@@ -135,4 +134,6 @@ var setVersion = function(){
|
|||||||
if(document.location.href.indexOf(host) == -1)
|
if(document.location.href.indexOf(host) == -1)
|
||||||
osd();
|
osd();
|
||||||
else
|
else
|
||||||
setVersion();
|
setVersion();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -27,36 +27,31 @@ class Automation(Plugin):
|
|||||||
|
|
||||||
return self.getIMDBids()
|
return self.getIMDBids()
|
||||||
|
|
||||||
def search(self, name, year = None):
|
def search(self, name, year = None, imdb_only = False):
|
||||||
result = fireEvent('movie.search', q = '%s %s' % (name, year if year else ''), limit = 1, merge = True)
|
result = fireEvent('movie.search', q = '%s %s' % (name, year if year else ''), limit = 1, merge = True)
|
||||||
|
|
||||||
if len(result) > 0:
|
if len(result) > 0:
|
||||||
return result[0].get('imdb')
|
return result[0].get('imdb') if imdb_only else result[0]
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def isMinimalMovie(self, movie):
|
def isMinimalMovie(self, movie):
|
||||||
|
if not movie.get('rating'):
|
||||||
|
return False
|
||||||
|
|
||||||
if movie['rating'] and movie['rating'].get('imdb'):
|
if movie['rating'] and movie['rating'].get('imdb'):
|
||||||
movie['votes'] = movie['rating']['imdb'][1]
|
movie['votes'] = movie['rating']['imdb'][1]
|
||||||
movie['rating'] = movie['rating']['imdb'][0]
|
movie['rating'] = movie['rating']['imdb'][0]
|
||||||
identifier = movie['imdb']
|
|
||||||
for minimal_type in ['year', 'rating', 'votes']:
|
for minimal_type in ['year', 'rating', 'votes']:
|
||||||
type_value = movie.get(minimal_type, 0)
|
type_value = movie.get(minimal_type, 0)
|
||||||
type_min = self.getMinimal(minimal_type)
|
type_min = self.getMinimal(minimal_type)
|
||||||
if type_value < type_min:
|
if type_value < type_min:
|
||||||
log.info('%s too low for %s, need %s has %s', (minimal_type, identifier, type_min, type_value))
|
log.info('%s too low for %s, need %s has %s', (minimal_type, movie['imdb'], type_min, type_value))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def getIMDBFromTitle(self, name, year = None):
|
|
||||||
result = fireEvent('movie.search', q = '%s %s' % (name, year), limit = 1, merge = True)
|
|
||||||
|
|
||||||
if len(result) > 0:
|
|
||||||
return result[0]
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def getMinimal(self, min_type):
|
def getMinimal(self, min_type):
|
||||||
return Env.setting(min_type, 'automation')
|
return Env.setting(min_type, 'automation')
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from couchpotato.core.helpers.rss import RSS
|
from couchpotato.core.helpers.rss import RSS
|
||||||
from couchpotato.core.helpers.variable import md5
|
from couchpotato.core.helpers.variable import md5, tryInt
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.automation.base import Automation
|
from couchpotato.core.providers.automation.base import Automation
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
@@ -19,41 +19,25 @@ class Bluray(Automation, RSS):
|
|||||||
return
|
return
|
||||||
|
|
||||||
movies = []
|
movies = []
|
||||||
RSSMovie = {'name': 'placeholder', 'year' : 'placeholder'}
|
|
||||||
RSSMovies = []
|
|
||||||
|
|
||||||
cache_key = 'bluray.%s' % md5(self.rss_url)
|
cache_key = 'bluray.%s' % md5(self.rss_url)
|
||||||
rss_data = self.getCache(cache_key, self.rss_url)
|
rss_data = self.getCache(cache_key, self.rss_url)
|
||||||
data = XMLTree.fromstring(rss_data)
|
data = XMLTree.fromstring(rss_data)
|
||||||
|
|
||||||
if data:
|
if data is not None:
|
||||||
rss_movies = self.getElements(data, 'channel/item')
|
rss_movies = self.getElements(data, 'channel/item')
|
||||||
|
|
||||||
for movie in rss_movies:
|
for movie in rss_movies:
|
||||||
RSSMovie['name'] = self.getTextElement(movie, "title").lower().split("blu-ray")[0].strip("(").rstrip()
|
name = self.getTextElement(movie, "title").lower().split("blu-ray")[0].strip("(").rstrip()
|
||||||
RSSMovie['year'] = self.getTextElement(movie, "description").split("|")[1].strip("(").strip()
|
year = self.getTextElement(movie, "description").split("|")[1].strip("(").strip()
|
||||||
|
|
||||||
if not RSSMovie['name'].find("/") == -1: # make sure it is not a double movie release
|
if not name.find("/") == -1: # make sure it is not a double movie release
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if int(RSSMovie['year']) < Env.setting('year', 'automation'): #do year filtering
|
if tryInt(year) < self.getMinimal('year'):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for test in RSSMovies:
|
imdb = self.search(name, year)
|
||||||
if test.values() == RSSMovie.values(): # make sure we did not already include it...
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
log.info('Release found: %s.' % RSSMovie)
|
|
||||||
RSSMovies.append(RSSMovie.copy())
|
|
||||||
|
|
||||||
if not RSSMovies:
|
|
||||||
log.info('No movies found.')
|
|
||||||
return
|
|
||||||
|
|
||||||
log.debug("Applying IMDB filter to found movies...")
|
|
||||||
|
|
||||||
for RSSMovie in RSSMovies:
|
|
||||||
imdb = self.getIMDBFromTitle(RSSMovie['name'] + ' ' + RSSMovie['year'])
|
|
||||||
|
|
||||||
if imdb:
|
if imdb:
|
||||||
if self.isMinimalMovie(imdb):
|
if self.isMinimalMovie(imdb):
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ class Kinepolis(Automation, RSS):
|
|||||||
return
|
return
|
||||||
|
|
||||||
movies = []
|
movies = []
|
||||||
RSSMovie = {'name': 'placeholder', 'year' : 'placeholder'}
|
|
||||||
|
|
||||||
cache_key = 'kinepolis.%s' % md5(self.rss_url)
|
cache_key = 'kinepolis.%s' % md5(self.rss_url)
|
||||||
rss_data = self.getCache(cache_key, self.rss_url)
|
rss_data = self.getCache(cache_key, self.rss_url)
|
||||||
@@ -29,14 +28,12 @@ class Kinepolis(Automation, RSS):
|
|||||||
rss_movies = self.getElements(data, 'channel/item')
|
rss_movies = self.getElements(data, 'channel/item')
|
||||||
|
|
||||||
for movie in rss_movies:
|
for movie in rss_movies:
|
||||||
RSSMovie['name'] = self.getTextElement(movie, "title")
|
name = self.getTextElement(movie, "title")
|
||||||
currentYear = datetime.datetime.now().strftime("%Y")
|
year = datetime.datetime.now().strftime("%Y")
|
||||||
RSSMovie['year'] = currentYear
|
|
||||||
|
|
||||||
log.debug('Release found: %s.', RSSMovie)
|
imdb = self.search(name, year)
|
||||||
imdb = self.getIMDBFromTitle(RSSMovie['name'], RSSMovie['year'])
|
|
||||||
|
|
||||||
if imdb:
|
if imdb and self.isMinimalMovie(imdb):
|
||||||
movies.append(imdb['imdb'])
|
movies.append(imdb['imdb'])
|
||||||
|
|
||||||
return movies
|
return movies
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
from .main import MoviesIO
|
||||||
|
|
||||||
|
def start():
|
||||||
|
return MoviesIO()
|
||||||
|
|
||||||
|
config = [{
|
||||||
|
'name': 'moviesio',
|
||||||
|
'groups': [
|
||||||
|
{
|
||||||
|
'tab': 'automation',
|
||||||
|
'name': 'moviesio',
|
||||||
|
'label': 'Movies.IO',
|
||||||
|
'description': 'Imports movies from <a href="http://movies.io">Movies.io</a> RSS watchlists',
|
||||||
|
'options': [
|
||||||
|
{
|
||||||
|
'name': 'automation_enabled',
|
||||||
|
'default': False,
|
||||||
|
'type': 'enabler',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'automation_urls_use',
|
||||||
|
'label': 'Use',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'automation_urls',
|
||||||
|
'label': 'url',
|
||||||
|
'type': 'combined',
|
||||||
|
'combine': ['automation_urls_use', 'automation_urls'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}]
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
from couchpotato.core.event import fireEvent
|
||||||
|
from couchpotato.core.helpers.rss import RSS
|
||||||
|
from couchpotato.core.helpers.variable import md5
|
||||||
|
from couchpotato.core.logger import CPLog
|
||||||
|
from couchpotato.core.providers.automation.base import Automation
|
||||||
|
from couchpotato.environment import Env
|
||||||
|
from dateutil.parser import parse
|
||||||
|
from xml.etree.ElementTree import ParseError
|
||||||
|
import time
|
||||||
|
import traceback
|
||||||
|
import xml.etree.ElementTree as XMLTree
|
||||||
|
|
||||||
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class MoviesIO(Automation, RSS):
|
||||||
|
|
||||||
|
interval = 1800
|
||||||
|
|
||||||
|
def getIMDBids(self):
|
||||||
|
|
||||||
|
if self.isDisabled():
|
||||||
|
return
|
||||||
|
|
||||||
|
movies = []
|
||||||
|
|
||||||
|
enablers = self.conf('automation_urls_use').split(',')
|
||||||
|
|
||||||
|
index = -1
|
||||||
|
for rss_url in self.conf('automation_urls').split(','):
|
||||||
|
|
||||||
|
index += 1
|
||||||
|
if not enablers[index]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
prop_name = 'automation.moviesio.last_update.%s' % md5(rss_url)
|
||||||
|
last_update = float(Env.prop(prop_name, default = 0))
|
||||||
|
|
||||||
|
last_movie_added = 0
|
||||||
|
try:
|
||||||
|
cache_key = 'imdb.rss.%s' % md5(rss_url)
|
||||||
|
|
||||||
|
rss_data = self.getCache(cache_key, rss_url, headers = {'Referer': ''})
|
||||||
|
data = XMLTree.fromstring(rss_data)
|
||||||
|
rss_movies = self.getElements(data, 'channel/item')
|
||||||
|
|
||||||
|
for movie in rss_movies:
|
||||||
|
created = int(time.mktime(parse(self.getTextElement(movie, "pubDate")).timetuple()))
|
||||||
|
|
||||||
|
if created > last_movie_added:
|
||||||
|
last_movie_added = created
|
||||||
|
if created <= last_update:
|
||||||
|
continue
|
||||||
|
|
||||||
|
nameyear = fireEvent('scanner.name_year', self.getTextElement(movie, "title"), single = True)
|
||||||
|
imdb = self.search(nameyear.get('name'), nameyear.get('year'), imdb_only = True)
|
||||||
|
|
||||||
|
if not imdb:
|
||||||
|
continue
|
||||||
|
|
||||||
|
movies.append(imdb)
|
||||||
|
except ParseError:
|
||||||
|
log.debug('Failed loading Movies.io watchlist, probably empty: %s', (rss_url))
|
||||||
|
except:
|
||||||
|
log.error('Failed loading Movies.io watchlist: %s %s', (rss_url, traceback.format_exc()))
|
||||||
|
|
||||||
|
Env.prop(prop_name, last_movie_added)
|
||||||
|
|
||||||
|
return movies
|
||||||
@@ -1,14 +1,13 @@
|
|||||||
from couchpotato.core.event import addEvent
|
from couchpotato.core.event import addEvent
|
||||||
|
from couchpotato.core.helpers.variable import tryFloat
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
from urlparse import urlparse
|
from urlparse import urlparse
|
||||||
from urllib import quote_plus
|
|
||||||
from couchpotato.core.helpers.encoding import simplifyString
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -86,19 +85,19 @@ class YarrProvider(Provider):
|
|||||||
def parseSize(self, size):
|
def parseSize(self, size):
|
||||||
|
|
||||||
sizeRaw = size.lower()
|
sizeRaw = size.lower()
|
||||||
size = float(re.sub(r'[^0-9.]', '', size).strip())
|
size = tryFloat(re.sub(r'[^0-9.]', '', size).strip())
|
||||||
|
|
||||||
for s in self.sizeGb:
|
for s in self.sizeGb:
|
||||||
if s in sizeRaw:
|
if s in sizeRaw:
|
||||||
return int(size) * 1024
|
return size * 1024
|
||||||
|
|
||||||
for s in self.sizeMb:
|
for s in self.sizeMb:
|
||||||
if s in sizeRaw:
|
if s in sizeRaw:
|
||||||
return int(size)
|
return size
|
||||||
|
|
||||||
for s in self.sizeKb:
|
for s in self.sizeKb:
|
||||||
if s in sizeRaw:
|
if s in sizeRaw:
|
||||||
return int(size) / 1024
|
return size / 1024
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|||||||
@@ -13,15 +13,14 @@ log = CPLog(__name__)
|
|||||||
class IMDBAPI(MovieProvider):
|
class IMDBAPI(MovieProvider):
|
||||||
|
|
||||||
urls = {
|
urls = {
|
||||||
'search': 'http://www.imdbapi.com/?%s',
|
'search': 'http://www.imdbapi.com/?tomatoes=true&%s',
|
||||||
'info': 'http://www.imdbapi.com/?i=%s',
|
'info': 'http://www.imdbapi.com/?tomatoes=true&i=%s',
|
||||||
}
|
}
|
||||||
|
|
||||||
http_time_between_calls = 0
|
http_time_between_calls = 0
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
addEvent('movie.search', self.search)
|
addEvent('movie.search', self.search)
|
||||||
addEvent('movie.searchimdb', self.search)
|
|
||||||
addEvent('movie.info', self.getInfo)
|
addEvent('movie.info', self.getInfo)
|
||||||
|
|
||||||
def search(self, q, limit = 12):
|
def search(self, q, limit = 12):
|
||||||
@@ -72,7 +71,7 @@ class IMDBAPI(MovieProvider):
|
|||||||
log.info('No proper json to decode')
|
log.info('No proper json to decode')
|
||||||
return movie_data
|
return movie_data
|
||||||
|
|
||||||
if movie.get('Response') == 'Parse Error':
|
if movie.get('Response') == 'Parse Error' or movie.get('Response') == 'False':
|
||||||
return movie_data
|
return movie_data
|
||||||
|
|
||||||
tmp_movie = movie.copy()
|
tmp_movie = movie.copy()
|
||||||
@@ -91,7 +90,7 @@ class IMDBAPI(MovieProvider):
|
|||||||
},
|
},
|
||||||
'rating': {
|
'rating': {
|
||||||
'imdb': (tryFloat(movie.get('imdbRating', 0)), tryInt(movie.get('imdbVotes', '').replace(',', ''))),
|
'imdb': (tryFloat(movie.get('imdbRating', 0)), tryInt(movie.get('imdbVotes', '').replace(',', ''))),
|
||||||
#'rotten': (tryFloat(movie.get('tomatoRating', 0)), tryInt(movie.get('tomatoReviews', 0))),
|
'rotten': (tryFloat(movie.get('tomatoRating', 0)), tryInt(movie.get('tomatoReviews', '').replace(',', ''))),
|
||||||
},
|
},
|
||||||
'imdb': str(movie.get('imdbID', '')),
|
'imdb': str(movie.get('imdbID', '')),
|
||||||
'runtime': self.runtimeToMinutes(movie.get('Runtime', '')),
|
'runtime': self.runtimeToMinutes(movie.get('Runtime', '')),
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ class NZBMatrix(NZBProvider, RSS):
|
|||||||
'username': self.conf('username'),
|
'username': self.conf('username'),
|
||||||
'apikey': self.conf('api_key'),
|
'apikey': self.conf('api_key'),
|
||||||
'searchin': 'weblink',
|
'searchin': 'weblink',
|
||||||
'age': Env.setting('retention', section = 'nzb'),
|
'maxage': Env.setting('retention', section = 'nzb'),
|
||||||
'english': self.conf('english_only'),
|
'english': self.conf('english_only'),
|
||||||
})
|
})
|
||||||
url = "%s?%s" % (self.urls['search'], arguments)
|
url = "%s?%s" % (self.urls['search'], arguments)
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from couchpotato.core.event import fireEvent
|
from couchpotato.core.event import fireEvent
|
||||||
from couchpotato.core.helpers.encoding import simplifyString, tryUrlencode, \
|
from couchpotato.core.helpers.encoding import tryUrlencode, toUnicode
|
||||||
toUnicode
|
from couchpotato.core.helpers.variable import tryInt
|
||||||
from couchpotato.core.helpers.variable import getTitle, tryInt
|
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.torrent.base import TorrentProvider
|
from couchpotato.core.providers.torrent.base import TorrentProvider
|
||||||
import traceback
|
import traceback
|
||||||
@@ -39,9 +38,10 @@ class SceneAccess(TorrentProvider):
|
|||||||
self.getCatId(quality['identifier'])[0]
|
self.getCatId(quality['identifier'])[0]
|
||||||
)
|
)
|
||||||
|
|
||||||
q = '"%s %s" %s' % (simplifyString(getTitle(movie['library'])), movie['library']['year'], quality.get('identifier'))
|
q = '%s %s' % (movie['library']['identifier'], quality.get('identifier'))
|
||||||
arguments = tryUrlencode({
|
arguments = tryUrlencode({
|
||||||
'search': q,
|
'search': q,
|
||||||
|
'method': 1,
|
||||||
})
|
})
|
||||||
url = "%s&%s" % (url, arguments)
|
url = "%s&%s" % (url, arguments)
|
||||||
|
|
||||||
@@ -57,7 +57,10 @@ class SceneAccess(TorrentProvider):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
resultsTable = html.find('table', attrs = {'id' : 'torrents-table'})
|
resultsTable = html.find('table', attrs = {'id' : 'torrents-table'})
|
||||||
entries = resultsTable.findAll('tr', attrs = {'class' : 'tt_row'})
|
if resultsTable is None:
|
||||||
|
return results
|
||||||
|
|
||||||
|
entries = resultsTable.find_all('tr', attrs = {'class' : 'tt_row'})
|
||||||
for result in entries:
|
for result in entries:
|
||||||
|
|
||||||
link = result.find('td', attrs = {'class' : 'ttr_name'}).find('a')
|
link = result.find('td', attrs = {'class' : 'ttr_name'}).find('a')
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
from .main import MoviesIO
|
||||||
|
|
||||||
|
def start():
|
||||||
|
return MoviesIO()
|
||||||
|
|
||||||
|
config = []
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
from couchpotato.core.providers.userscript.base import UserscriptBase
|
||||||
|
|
||||||
|
|
||||||
|
class MoviesIO(UserscriptBase):
|
||||||
|
|
||||||
|
includes = ['*://movies.io/m/*']
|
||||||
@@ -4,7 +4,10 @@ from couchpotato.core.providers.userscript.base import UserscriptBase
|
|||||||
|
|
||||||
class RottenTomatoes(UserscriptBase):
|
class RottenTomatoes(UserscriptBase):
|
||||||
|
|
||||||
includes = ['*://www.rottentomatoes.com/m/*']
|
includes = ['*://www.rottentomatoes.com/m/*/']
|
||||||
|
excludes = ['*://www.rottentomatoes.com/m/*/*/']
|
||||||
|
|
||||||
|
version = 2
|
||||||
|
|
||||||
def getMovie(self, url):
|
def getMovie(self, url):
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -28,7 +28,7 @@ pidfile=${CP_PIDFILE-/var/run/couchpotato/couchpotato.pid}
|
|||||||
##
|
##
|
||||||
|
|
||||||
pidpath=`dirname ${pidfile}`
|
pidpath=`dirname ${pidfile}`
|
||||||
options=" --daemon --pidfile=${pidfile} --datadir=${datadir}"
|
options=" --daemon --pid_file=${pidfile} --datadir=${datadir}"
|
||||||
|
|
||||||
# create PID directory if not exist and ensure the couchpotato user can write to it
|
# create PID directory if not exist and ensure the couchpotato user can write to it
|
||||||
if [ ! -d $pidpath ]; then
|
if [ ! -d $pidpath ]; then
|
||||||
|
|||||||
@@ -258,12 +258,6 @@ def bind_sockets(port, address=None, family=socket.AF_UNSPEC, backlog=128):
|
|||||||
if address == "":
|
if address == "":
|
||||||
address = None
|
address = None
|
||||||
flags = socket.AI_PASSIVE
|
flags = socket.AI_PASSIVE
|
||||||
if hasattr(socket, "AI_ADDRCONFIG"):
|
|
||||||
# AI_ADDRCONFIG ensures that we only try to bind on ipv6
|
|
||||||
# if the system is configured for it, but the flag doesn't
|
|
||||||
# exist on some platforms (specifically WinXP, although
|
|
||||||
# newer versions of windows have it)
|
|
||||||
flags |= socket.AI_ADDRCONFIG
|
|
||||||
for res in set(socket.getaddrinfo(address, port, family, socket.SOCK_STREAM,
|
for res in set(socket.getaddrinfo(address, port, family, socket.SOCK_STREAM,
|
||||||
0, flags)):
|
0, flags)):
|
||||||
af, socktype, proto, canonname, sockaddr = res
|
af, socktype, proto, canonname, sockaddr = res
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
VERSION = '2.0.0.pre1'
|
VERSION = '2.0.0.pre1'
|
||||||
|
BRANCH = 'desktop'
|
||||||
|
|||||||
Reference in New Issue
Block a user