Merge branch 'refs/heads/develop' into tv
Conflicts: couchpotato/core/media/movie/_base/main.py couchpotato/core/plugins/release/main.py couchpotato/core/plugins/renamer/main.py
This commit is contained in:
@@ -49,13 +49,13 @@ class Downloader(Provider):
|
||||
|
||||
return []
|
||||
|
||||
def _download(self, data = None, movie = None, manual = False, filedata = None):
|
||||
if not movie: movie = {}
|
||||
def _download(self, data = None, media = None, manual = False, filedata = None):
|
||||
if not media: media = {}
|
||||
if not data: data = {}
|
||||
|
||||
if self.isDisabled(manual, data):
|
||||
return
|
||||
return self.download(data = data, movie = movie, filedata = filedata)
|
||||
return self.download(data = data, media = media, filedata = filedata)
|
||||
|
||||
def _getAllDownloadStatus(self):
|
||||
if self.isDisabled(manual = True, data = {}):
|
||||
|
||||
@@ -12,8 +12,8 @@ class Blackhole(Downloader):
|
||||
|
||||
protocol = ['nzb', 'torrent', 'torrent_magnet']
|
||||
|
||||
def download(self, data = None, movie = None, filedata = None):
|
||||
if not movie: movie = {}
|
||||
def download(self, data = None, media = None, filedata = None):
|
||||
if not media: media = {}
|
||||
if not data: data = {}
|
||||
|
||||
directory = self.conf('directory')
|
||||
@@ -33,7 +33,7 @@ class Blackhole(Downloader):
|
||||
log.error('No nzb/torrent available: %s', data.get('url'))
|
||||
return False
|
||||
|
||||
file_name = self.createFileName(data, filedata, movie)
|
||||
file_name = self.createFileName(data, filedata, media)
|
||||
full_path = os.path.join(directory, file_name)
|
||||
|
||||
if self.conf('create_subdir'):
|
||||
|
||||
@@ -32,7 +32,10 @@ class Deluge(Downloader):
|
||||
|
||||
return self.drpc
|
||||
|
||||
def download(self, data, movie, filedata = None):
|
||||
def download(self, data = None, media = None, filedata = None):
|
||||
if not media: media = {}
|
||||
if not data: data = {}
|
||||
|
||||
log.info('Sending "%s" (%s) to Deluge.', (data.get('name'), data.get('protocol')))
|
||||
|
||||
if not self.connect():
|
||||
@@ -73,7 +76,7 @@ class Deluge(Downloader):
|
||||
if data.get('protocol') == 'torrent_magnet':
|
||||
remote_torrent = self.drpc.add_torrent_magnet(data.get('url'), options)
|
||||
else:
|
||||
filename = self.createFileName(data, filedata, movie)
|
||||
filename = self.createFileName(data, filedata, media)
|
||||
remote_torrent = self.drpc.add_torrent_file(filename, filedata, options)
|
||||
|
||||
if not remote_torrent:
|
||||
|
||||
@@ -19,8 +19,8 @@ class NZBGet(Downloader):
|
||||
|
||||
url = 'http://%(username)s:%(password)s@%(host)s/xmlrpc'
|
||||
|
||||
def download(self, data = None, movie = None, filedata = None):
|
||||
if not movie: movie = {}
|
||||
def download(self, data = None, media = None, filedata = None):
|
||||
if not media: media = {}
|
||||
if not data: data = {}
|
||||
|
||||
if not filedata:
|
||||
@@ -30,7 +30,7 @@ class NZBGet(Downloader):
|
||||
log.info('Sending "%s" to NZBGet.', data.get('name'))
|
||||
|
||||
url = self.url % {'host': self.conf('host'), 'username': self.conf('username'), 'password': self.conf('password')}
|
||||
nzb_name = ss('%s.nzb' % self.createNzbName(data, movie))
|
||||
nzb_name = ss('%s.nzb' % self.createNzbName(data, media))
|
||||
|
||||
rpc = xmlrpclib.ServerProxy(url)
|
||||
try:
|
||||
|
||||
@@ -23,13 +23,13 @@ class NZBVortex(Downloader):
|
||||
api_level = None
|
||||
session_id = None
|
||||
|
||||
def download(self, data = None, movie = None, filedata = None):
|
||||
if not movie: movie = {}
|
||||
def download(self, data = None, media = None, filedata = None):
|
||||
if not media: media = {}
|
||||
if not data: data = {}
|
||||
|
||||
# Send the nzb
|
||||
try:
|
||||
nzb_filename = self.createFileName(data, filedata, movie)
|
||||
nzb_filename = self.createFileName(data, filedata, media)
|
||||
self.call('nzb/add', params = {'file': (nzb_filename, filedata)}, multipart = True)
|
||||
|
||||
raw_statuses = self.call('nzb')
|
||||
|
||||
@@ -12,8 +12,8 @@ class Pneumatic(Downloader):
|
||||
protocol = ['nzb']
|
||||
strm_syntax = 'plugin://plugin.program.pneumatic/?mode=strm&type=add_file&nzb=%s&nzbname=%s'
|
||||
|
||||
def download(self, data = None, movie = None, filedata = None):
|
||||
if not movie: movie = {}
|
||||
def download(self, data = None, media = None, filedata = None):
|
||||
if not media: media = {}
|
||||
if not data: data = {}
|
||||
|
||||
directory = self.conf('directory')
|
||||
@@ -25,7 +25,7 @@ class Pneumatic(Downloader):
|
||||
log.error('No nzb available!')
|
||||
return False
|
||||
|
||||
fullPath = os.path.join(directory, self.createFileName(data, filedata, movie))
|
||||
fullPath = os.path.join(directory, self.createFileName(data, filedata, media))
|
||||
|
||||
try:
|
||||
if not os.path.isfile(fullPath):
|
||||
@@ -33,7 +33,7 @@ class Pneumatic(Downloader):
|
||||
with open(fullPath, 'wb') as f:
|
||||
f.write(filedata)
|
||||
|
||||
nzb_name = self.createNzbName(data, movie)
|
||||
nzb_name = self.createNzbName(data, media)
|
||||
strm_path = os.path.join(directory, nzb_name)
|
||||
|
||||
strm_file = open(strm_path + '.strm', 'wb')
|
||||
|
||||
@@ -77,7 +77,10 @@ class rTorrent(Downloader):
|
||||
return True
|
||||
|
||||
|
||||
def download(self, data, movie, filedata = None):
|
||||
def download(self, data = None, media = None, filedata = None):
|
||||
if not media: media = {}
|
||||
if not data: data = {}
|
||||
|
||||
log.debug('Sending "%s" to rTorrent.', (data.get('name')))
|
||||
|
||||
if not self.connect():
|
||||
|
||||
@@ -16,8 +16,8 @@ class Sabnzbd(Downloader):
|
||||
|
||||
protocol = ['nzb']
|
||||
|
||||
def download(self, data = None, movie = None, filedata = None):
|
||||
if not movie: movie = {}
|
||||
def download(self, data = None, media = None, filedata = None):
|
||||
if not media: media = {}
|
||||
if not data: data = {}
|
||||
|
||||
log.info('Sending "%s" to SABnzbd.', data.get('name'))
|
||||
@@ -25,7 +25,7 @@ class Sabnzbd(Downloader):
|
||||
req_params = {
|
||||
'cat': self.conf('category'),
|
||||
'mode': 'addurl',
|
||||
'nzbname': self.createNzbName(data, movie),
|
||||
'nzbname': self.createNzbName(data, media),
|
||||
'priority': self.conf('priority'),
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ class Sabnzbd(Downloader):
|
||||
return False
|
||||
|
||||
# If it's a .rar, it adds the .rar extension, otherwise it stays .nzb
|
||||
nzb_filename = self.createFileName(data, filedata, movie)
|
||||
nzb_filename = self.createFileName(data, filedata, media)
|
||||
req_params['mode'] = 'addfile'
|
||||
else:
|
||||
req_params['name'] = data.get('url')
|
||||
|
||||
@@ -13,8 +13,8 @@ class Synology(Downloader):
|
||||
protocol = ['nzb', 'torrent', 'torrent_magnet']
|
||||
log = CPLog(__name__)
|
||||
|
||||
def download(self, data = None, movie = None, filedata = None):
|
||||
if not movie: movie = {}
|
||||
def download(self, data = None, media = None, filedata = None):
|
||||
if not media: media = {}
|
||||
if not data: data = {}
|
||||
|
||||
response = False
|
||||
|
||||
@@ -31,7 +31,9 @@ class Transmission(Downloader):
|
||||
|
||||
return self.trpc
|
||||
|
||||
def download(self, data, movie, filedata = None):
|
||||
def download(self, data = None, media = None, filedata = None):
|
||||
if not media: media = {}
|
||||
if not data: data = {}
|
||||
|
||||
log.info('Sending "%s" (%s) to Transmission.', (data.get('name'), data.get('protocol')))
|
||||
|
||||
|
||||
@@ -36,11 +36,6 @@ config = [{
|
||||
'name': 'label',
|
||||
'description': 'Label to add torrent as.',
|
||||
},
|
||||
{
|
||||
'name': 'directory',
|
||||
'type': 'directory',
|
||||
'description': 'Download to this directory. Keep empty for default uTorrent download directory.',
|
||||
},
|
||||
{
|
||||
'name': 'remove_complete',
|
||||
'label': 'Remove torrent',
|
||||
|
||||
@@ -36,8 +36,8 @@ class uTorrent(Downloader):
|
||||
|
||||
return self.utorrent_api
|
||||
|
||||
def download(self, data = None, movie = None, filedata = None):
|
||||
if not movie: movie = {}
|
||||
def download(self, data = None, media = None, filedata = None):
|
||||
if not media: media = {}
|
||||
if not data: data = {}
|
||||
|
||||
log.debug('Sending "%s" (%s) to uTorrent.', (data.get('name'), data.get('protocol')))
|
||||
@@ -78,7 +78,7 @@ class uTorrent(Downloader):
|
||||
info = bdecode(filedata)["info"]
|
||||
torrent_hash = sha1(benc(info)).hexdigest().upper()
|
||||
|
||||
torrent_filename = self.createFileName(data, filedata, movie)
|
||||
torrent_filename = self.createFileName(data, filedata, media)
|
||||
|
||||
if data.get('seed_ratio'):
|
||||
torrent_params['seed_override'] = 1
|
||||
@@ -92,17 +92,11 @@ class uTorrent(Downloader):
|
||||
if len(torrent_hash) == 32:
|
||||
torrent_hash = b16encode(b32decode(torrent_hash))
|
||||
|
||||
# Set download directory
|
||||
if self.conf('directory'):
|
||||
directory = self.conf('directory')
|
||||
else:
|
||||
directory = False
|
||||
|
||||
# Send request to uTorrent
|
||||
if data.get('protocol') == 'torrent_magnet':
|
||||
self.utorrent_api.add_torrent_uri(torrent_filename, data.get('url'), directory)
|
||||
self.utorrent_api.add_torrent_uri(torrent_filename, data.get('url'))
|
||||
else:
|
||||
self.utorrent_api.add_torrent_file(torrent_filename, filedata, directory)
|
||||
self.utorrent_api.add_torrent_file(torrent_filename, filedata)
|
||||
|
||||
# Change settings of added torrent
|
||||
self.utorrent_api.set_torrent(torrent_hash, torrent_params)
|
||||
@@ -256,13 +250,13 @@ class uTorrentAPI(object):
|
||||
def add_torrent_uri(self, filename, torrent, add_folder = False):
|
||||
action = "action=add-url&s=%s" % urllib.quote(torrent)
|
||||
if add_folder:
|
||||
action += "&path=%s" % urllib.quote(add_folder)
|
||||
action += "&path=%s" % urllib.quote(filename)
|
||||
return self._request(action)
|
||||
|
||||
def add_torrent_file(self, filename, filedata, add_folder = False):
|
||||
action = "action=add-file"
|
||||
if add_folder:
|
||||
action += "&path=%s" % urllib.quote(add_folder)
|
||||
action += "&path=%s" % urllib.quote(filename)
|
||||
return self._request(action, {"torrent_file": (ss(filename), filedata)})
|
||||
|
||||
def set_torrent(self, hash, params):
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
from couchpotato import get_session
|
||||
from couchpotato.api import addApiView
|
||||
from couchpotato.core.event import fireEvent, fireEventAsync, addEvent
|
||||
from couchpotato.core.helpers.variable import splitString
|
||||
from couchpotato.core.helpers.encoding import toUnicode
|
||||
from couchpotato.core.helpers.variable import mergeDicts, splitString, getImdb
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.media import MediaBase
|
||||
from couchpotato.core.settings.model import Media
|
||||
from couchpotato.core.settings.model import Library, LibraryTitle, Release, \
|
||||
Media
|
||||
from sqlalchemy.orm import joinedload_all
|
||||
from sqlalchemy.sql.expression import or_, asc, not_, desc
|
||||
from string import ascii_lowercase
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
@@ -20,7 +25,49 @@ class MediaPlugin(MediaBase):
|
||||
}
|
||||
})
|
||||
|
||||
addEvent('app.load', self.addSingleRefresh)
|
||||
addApiView('media.list', self.listView, docs = {
|
||||
'desc': 'List media',
|
||||
'params': {
|
||||
'type': {'type': 'string', 'desc': 'Media type to filter on.'},
|
||||
'status': {'type': 'array or csv', 'desc': 'Filter movie by status. Example:"active,done"'},
|
||||
'release_status': {'type': 'array or csv', 'desc': 'Filter movie by status of its releases. Example:"snatched,available"'},
|
||||
'limit_offset': {'desc': 'Limit and offset the movie list. Examples: "50" or "50,30"'},
|
||||
'starts_with': {'desc': 'Starts with these characters. Example: "a" returns all movies starting with the letter "a"'},
|
||||
'search': {'desc': 'Search movie title'},
|
||||
},
|
||||
'return': {'type': 'object', 'example': """{
|
||||
'success': True,
|
||||
'empty': bool, any movies returned or not,
|
||||
'media': array, media found,
|
||||
}"""}
|
||||
})
|
||||
|
||||
addApiView('media.get', self.getView, docs = {
|
||||
'desc': 'Get media by id',
|
||||
'params': {
|
||||
'id': {'desc': 'The id of the media'},
|
||||
}
|
||||
})
|
||||
|
||||
addApiView('media.delete', self.deleteView, docs = {
|
||||
'desc': 'Delete a media from the wanted list',
|
||||
'params': {
|
||||
'id': {'desc': 'Media ID(s) you want to delete.', 'type': 'int (comma separated)'},
|
||||
'delete_from': {'desc': 'Delete media from this page', 'type': 'string: all (default), wanted, manage'},
|
||||
}
|
||||
})
|
||||
|
||||
addApiView('media.available_chars', self.charView)
|
||||
|
||||
addEvent('app.load', self.addSingleRefreshView)
|
||||
addEvent('app.load', self.addSingleListView)
|
||||
addEvent('app.load', self.addSingleCharView)
|
||||
addEvent('app.load', self.addSingleDeleteView)
|
||||
|
||||
addEvent('media.get', self.get)
|
||||
addEvent('media.list', self.list)
|
||||
addEvent('media.delete', self.delete)
|
||||
addEvent('media.restatus', self.restatus)
|
||||
|
||||
def refresh(self, id = '', **kwargs):
|
||||
db = get_session()
|
||||
@@ -43,7 +90,369 @@ class MediaPlugin(MediaBase):
|
||||
'success': True,
|
||||
}
|
||||
|
||||
def addSingleRefresh(self):
|
||||
def addSingleRefreshView(self):
|
||||
|
||||
for media_type in fireEvent('media.types', merge = True):
|
||||
addApiView('%s.refresh' % media_type, self.refresh)
|
||||
|
||||
def get(self, media_id):
|
||||
|
||||
db = get_session()
|
||||
|
||||
imdb_id = getImdb(str(media_id))
|
||||
|
||||
if imdb_id:
|
||||
m = db.query(Media).filter(Media.library.has(identifier = imdb_id)).first()
|
||||
else:
|
||||
m = db.query(Media).filter_by(id = media_id).first()
|
||||
|
||||
results = None
|
||||
if m:
|
||||
results = m.to_dict(self.default_dict)
|
||||
|
||||
db.expire_all()
|
||||
return results
|
||||
|
||||
def getView(self, id = None, **kwargs):
|
||||
|
||||
media = self.get(id) if id else None
|
||||
|
||||
return {
|
||||
'success': media is not None,
|
||||
'media': media,
|
||||
}
|
||||
|
||||
def list(self, types = None, status = None, release_status = None, limit_offset = None, starts_with = None, search = None, order = None):
|
||||
|
||||
db = get_session()
|
||||
|
||||
# Make a list from string
|
||||
if status and not isinstance(status, (list, tuple)):
|
||||
status = [status]
|
||||
if release_status and not isinstance(release_status, (list, tuple)):
|
||||
release_status = [release_status]
|
||||
if types and not isinstance(types, (list, tuple)):
|
||||
types = [types]
|
||||
|
||||
# query movie ids
|
||||
q = db.query(Media) \
|
||||
.with_entities(Media.id) \
|
||||
.group_by(Media.id)
|
||||
|
||||
# Filter on movie status
|
||||
if status and len(status) > 0:
|
||||
statuses = fireEvent('status.get', status, single = len(status) > 1)
|
||||
statuses = [s.get('id') for s in statuses]
|
||||
|
||||
q = q.filter(Media.status_id.in_(statuses))
|
||||
|
||||
# Filter on release status
|
||||
if release_status and len(release_status) > 0:
|
||||
q = q.join(Media.releases)
|
||||
|
||||
statuses = fireEvent('status.get', release_status, single = len(release_status) > 1)
|
||||
statuses = [s.get('id') for s in statuses]
|
||||
|
||||
q = q.filter(Release.status_id.in_(statuses))
|
||||
|
||||
# Filter on type
|
||||
if types and len(types) > 0:
|
||||
try: q = q.filter(Media.type.in_(types))
|
||||
except: pass
|
||||
|
||||
# Only join when searching / ordering
|
||||
if starts_with or search or order != 'release_order':
|
||||
q = q.join(Media.library, Library.titles) \
|
||||
.filter(LibraryTitle.default == True)
|
||||
|
||||
# Add search filters
|
||||
filter_or = []
|
||||
if starts_with:
|
||||
starts_with = toUnicode(starts_with.lower())
|
||||
if starts_with in ascii_lowercase:
|
||||
filter_or.append(LibraryTitle.simple_title.startswith(starts_with))
|
||||
else:
|
||||
ignore = []
|
||||
for letter in ascii_lowercase:
|
||||
ignore.append(LibraryTitle.simple_title.startswith(toUnicode(letter)))
|
||||
filter_or.append(not_(or_(*ignore)))
|
||||
|
||||
if search:
|
||||
filter_or.append(LibraryTitle.simple_title.like('%%' + search + '%%'))
|
||||
|
||||
if len(filter_or) > 0:
|
||||
q = q.filter(or_(*filter_or))
|
||||
|
||||
total_count = q.count()
|
||||
if total_count == 0:
|
||||
return 0, []
|
||||
|
||||
if order == 'release_order':
|
||||
q = q.order_by(desc(Release.last_edit))
|
||||
else:
|
||||
q = q.order_by(asc(LibraryTitle.simple_title))
|
||||
|
||||
if limit_offset:
|
||||
splt = splitString(limit_offset) if isinstance(limit_offset, (str, unicode)) else limit_offset
|
||||
limit = splt[0]
|
||||
offset = 0 if len(splt) is 1 else splt[1]
|
||||
q = q.limit(limit).offset(offset)
|
||||
|
||||
# Get all media_ids in sorted order
|
||||
media_ids = [m.id for m in q.all()]
|
||||
|
||||
# List release statuses
|
||||
releases = db.query(Release) \
|
||||
.filter(Release.movie_id.in_(media_ids)) \
|
||||
.all()
|
||||
|
||||
release_statuses = dict((m, set()) for m in media_ids)
|
||||
releases_count = dict((m, 0) for m in media_ids)
|
||||
for release in releases:
|
||||
release_statuses[release.movie_id].add('%d,%d' % (release.status_id, release.quality_id))
|
||||
releases_count[release.movie_id] += 1
|
||||
|
||||
# Get main movie data
|
||||
q2 = db.query(Media) \
|
||||
.options(joinedload_all('library.titles')) \
|
||||
.options(joinedload_all('library.files')) \
|
||||
.options(joinedload_all('status')) \
|
||||
.options(joinedload_all('files'))
|
||||
|
||||
q2 = q2.filter(Media.id.in_(media_ids))
|
||||
|
||||
results = q2.all()
|
||||
|
||||
# Create dict by movie id
|
||||
movie_dict = {}
|
||||
for movie in results:
|
||||
movie_dict[movie.id] = movie
|
||||
|
||||
# List movies based on media_ids order
|
||||
movies = []
|
||||
for media_id in media_ids:
|
||||
|
||||
releases = []
|
||||
for r in release_statuses.get(media_id):
|
||||
x = splitString(r)
|
||||
releases.append({'status_id': x[0], 'quality_id': x[1]})
|
||||
|
||||
# Merge releases with movie dict
|
||||
movies.append(mergeDicts(movie_dict[media_id].to_dict({
|
||||
'library': {'titles': {}, 'files':{}},
|
||||
'files': {},
|
||||
}), {
|
||||
'releases': releases,
|
||||
'releases_count': releases_count.get(media_id),
|
||||
}))
|
||||
|
||||
db.expire_all()
|
||||
return total_count, movies
|
||||
|
||||
def listView(self, **kwargs):
|
||||
|
||||
types = splitString(kwargs.get('types'))
|
||||
status = splitString(kwargs.get('status'))
|
||||
release_status = splitString(kwargs.get('release_status'))
|
||||
limit_offset = kwargs.get('limit_offset')
|
||||
starts_with = kwargs.get('starts_with')
|
||||
search = kwargs.get('search')
|
||||
order = kwargs.get('order')
|
||||
|
||||
total_movies, movies = self.list(
|
||||
types = types,
|
||||
status = status,
|
||||
release_status = release_status,
|
||||
limit_offset = limit_offset,
|
||||
starts_with = starts_with,
|
||||
search = search,
|
||||
order = order
|
||||
)
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'empty': len(movies) == 0,
|
||||
'total': total_movies,
|
||||
'movies': movies,
|
||||
}
|
||||
|
||||
def addSingleListView(self):
|
||||
|
||||
for media_type in fireEvent('media.types', merge = True):
|
||||
def tempList(*args, **kwargs):
|
||||
return self.listView(types = media_type, *args, **kwargs)
|
||||
addApiView('%s.list' % media_type, tempList)
|
||||
|
||||
def availableChars(self, types = None, status = None, release_status = None):
|
||||
|
||||
types = types or []
|
||||
status = status or []
|
||||
release_status = release_status or []
|
||||
|
||||
db = get_session()
|
||||
|
||||
# Make a list from string
|
||||
if not isinstance(status, (list, tuple)):
|
||||
status = [status]
|
||||
if release_status and not isinstance(release_status, (list, tuple)):
|
||||
release_status = [release_status]
|
||||
if types and not isinstance(types, (list, tuple)):
|
||||
types = [types]
|
||||
|
||||
q = db.query(Media)
|
||||
|
||||
# Filter on movie status
|
||||
if status and len(status) > 0:
|
||||
statuses = fireEvent('status.get', status, single = len(release_status) > 1)
|
||||
statuses = [s.get('id') for s in statuses]
|
||||
|
||||
q = q.filter(Media.status_id.in_(statuses))
|
||||
|
||||
# Filter on release status
|
||||
if release_status and len(release_status) > 0:
|
||||
|
||||
statuses = fireEvent('status.get', release_status, single = len(release_status) > 1)
|
||||
statuses = [s.get('id') for s in statuses]
|
||||
|
||||
q = q.join(Media.releases) \
|
||||
.filter(Release.status_id.in_(statuses))
|
||||
|
||||
# Filter on type
|
||||
if types and len(types) > 0:
|
||||
try: q = q.filter(Media.type.in_(types))
|
||||
except: pass
|
||||
|
||||
q = q.join(Library, LibraryTitle) \
|
||||
.with_entities(LibraryTitle.simple_title) \
|
||||
.filter(LibraryTitle.default == True)
|
||||
|
||||
titles = q.all()
|
||||
|
||||
chars = set()
|
||||
for title in titles:
|
||||
try:
|
||||
char = title[0][0]
|
||||
char = char if char in ascii_lowercase else '#'
|
||||
chars.add(str(char))
|
||||
except:
|
||||
log.error('Failed getting title for %s', title.libraries_id)
|
||||
|
||||
if len(chars) == 25:
|
||||
break
|
||||
|
||||
db.expire_all()
|
||||
return ''.join(sorted(chars))
|
||||
|
||||
def charView(self, **kwargs):
|
||||
|
||||
type = splitString(kwargs.get('type', 'movie'))
|
||||
status = splitString(kwargs.get('status', None))
|
||||
release_status = splitString(kwargs.get('release_status', None))
|
||||
chars = self.availableChars(type, status, release_status)
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'empty': len(chars) == 0,
|
||||
'chars': chars,
|
||||
}
|
||||
|
||||
def addSingleCharView(self):
|
||||
|
||||
for media_type in fireEvent('media.types', merge = True):
|
||||
def tempChar(*args, **kwargs):
|
||||
return self.charView(types = media_type, *args, **kwargs)
|
||||
addApiView('%s.available_chars' % media_type, tempChar)
|
||||
|
||||
def delete(self, media_id, delete_from = None):
|
||||
|
||||
db = get_session()
|
||||
|
||||
media = db.query(Media).filter_by(id = media_id).first()
|
||||
if media:
|
||||
deleted = False
|
||||
if delete_from == 'all':
|
||||
db.delete(media)
|
||||
db.commit()
|
||||
deleted = True
|
||||
else:
|
||||
done_status = fireEvent('status.get', 'done', single = True)
|
||||
|
||||
total_releases = len(media.releases)
|
||||
total_deleted = 0
|
||||
new_movie_status = None
|
||||
for release in media.releases:
|
||||
if delete_from in ['wanted', 'snatched', 'late']:
|
||||
if release.status_id != done_status.get('id'):
|
||||
db.delete(release)
|
||||
total_deleted += 1
|
||||
new_movie_status = 'done'
|
||||
elif delete_from == 'manage':
|
||||
if release.status_id == done_status.get('id'):
|
||||
db.delete(release)
|
||||
total_deleted += 1
|
||||
new_movie_status = 'active'
|
||||
db.commit()
|
||||
|
||||
if total_releases == total_deleted:
|
||||
db.delete(media)
|
||||
db.commit()
|
||||
deleted = True
|
||||
elif new_movie_status:
|
||||
new_status = fireEvent('status.get', new_movie_status, single = True)
|
||||
media.profile_id = None
|
||||
media.status_id = new_status.get('id')
|
||||
db.commit()
|
||||
else:
|
||||
fireEvent('media.restatus', media.id, single = True)
|
||||
|
||||
if deleted:
|
||||
fireEvent('notify.frontend', type = 'movie.deleted', data = media.to_dict())
|
||||
|
||||
db.expire_all()
|
||||
return True
|
||||
|
||||
def deleteView(self, id = '', **kwargs):
|
||||
|
||||
ids = splitString(id)
|
||||
for media_id in ids:
|
||||
self.delete(media_id, delete_from = kwargs.get('delete_from', 'all'))
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
}
|
||||
|
||||
def addSingleDeleteView(self):
|
||||
|
||||
for media_type in fireEvent('media.types', merge = True):
|
||||
def tempDelete(*args, **kwargs):
|
||||
return self.deleteView(types = media_type, *args, **kwargs)
|
||||
addApiView('%s.delete' % media_type, tempDelete)
|
||||
|
||||
def restatus(self, media_id):
|
||||
|
||||
active_status, done_status = fireEvent('status.get', ['active', 'done'], single = True)
|
||||
|
||||
db = get_session()
|
||||
|
||||
m = db.query(Media).filter_by(id = media_id).first()
|
||||
if not m or len(m.library.titles) == 0:
|
||||
log.debug('Can\'t restatus movie, doesn\'t seem to exist.')
|
||||
return False
|
||||
|
||||
log.debug('Changing status for %s', m.library.titles[0].title)
|
||||
if not m.profile:
|
||||
m.status_id = done_status.get('id')
|
||||
else:
|
||||
move_to_wanted = True
|
||||
|
||||
for t in m.profile.types:
|
||||
for release in m.releases:
|
||||
if t.quality.identifier is release.quality.identifier and (release.status_id is done_status.get('id') and t.finish):
|
||||
move_to_wanted = False
|
||||
|
||||
m.status_id = active_status.get('id') if move_to_wanted else done_status.get('id')
|
||||
|
||||
db.commit()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -2,15 +2,10 @@ from couchpotato import get_session
|
||||
from couchpotato.api import addApiView
|
||||
from couchpotato.core.event import fireEvent, fireEventAsync, addEvent
|
||||
from couchpotato.core.helpers.encoding import toUnicode
|
||||
from couchpotato.core.helpers.variable import getImdb, splitString, tryInt, \
|
||||
mergeDicts
|
||||
from couchpotato.core.helpers.variable import splitString, tryInt
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.media.movie import MovieTypeBase
|
||||
from couchpotato.core.settings.model import Library, LibraryTitle, Media, \
|
||||
Release
|
||||
from sqlalchemy.orm import joinedload_all
|
||||
from sqlalchemy.sql.expression import or_, asc, not_, desc
|
||||
from string import ascii_lowercase
|
||||
from couchpotato.core.settings.model import Media
|
||||
import time
|
||||
|
||||
log = CPLog(__name__)
|
||||
@@ -26,28 +21,6 @@ class MovieBase(MovieTypeBase):
|
||||
super(MovieBase, self).__init__()
|
||||
self.initType()
|
||||
|
||||
addApiView('movie.list', self.listView, docs = {
|
||||
'desc': 'List movies in wanted list',
|
||||
'params': {
|
||||
'status': {'type': 'array or csv', 'desc': 'Filter movie by status. Example:"active,done"'},
|
||||
'release_status': {'type': 'array or csv', 'desc': 'Filter movie by status of its releases. Example:"snatched,available"'},
|
||||
'limit_offset': {'desc': 'Limit and offset the movie list. Examples: "50" or "50,30"'},
|
||||
'starts_with': {'desc': 'Starts with these characters. Example: "a" returns all movies starting with the letter "a"'},
|
||||
'search': {'desc': 'Search movie title'},
|
||||
},
|
||||
'return': {'type': 'object', 'example': """{
|
||||
'success': True,
|
||||
'empty': bool, any movies returned or not,
|
||||
'movies': array, movies found,
|
||||
}"""}
|
||||
})
|
||||
addApiView('movie.get', self.getView, docs = {
|
||||
'desc': 'Get a movie by id',
|
||||
'params': {
|
||||
'id': {'desc': 'The id of the movie'},
|
||||
}
|
||||
})
|
||||
addApiView('movie.available_chars', self.charView)
|
||||
addApiView('movie.add', self.addView, docs = {
|
||||
'desc': 'Add new movie to the wanted list',
|
||||
'params': {
|
||||
@@ -65,255 +38,8 @@ class MovieBase(MovieTypeBase):
|
||||
'default_title': {'desc': 'Movie title to use for searches. Has to be one of the titles returned by movie.search.'},
|
||||
}
|
||||
})
|
||||
addApiView('movie.delete', self.deleteView, docs = {
|
||||
'desc': 'Delete a movie from the wanted list',
|
||||
'params': {
|
||||
'id': {'desc': 'Movie ID(s) you want to delete.', 'type': 'int (comma separated)'},
|
||||
'delete_from': {'desc': 'Delete movie from this page', 'type': 'string: all (default), wanted, manage'},
|
||||
}
|
||||
})
|
||||
|
||||
addEvent('movie.add', self.add)
|
||||
addEvent('movie.delete', self.delete)
|
||||
addEvent('movie.get', self.get)
|
||||
addEvent('movie.list', self.list)
|
||||
addEvent('movie.restatus', self.restatus)
|
||||
|
||||
def getView(self, id = None, **kwargs):
|
||||
|
||||
movie = self.get(id) if id else None
|
||||
|
||||
return {
|
||||
'success': movie is not None,
|
||||
'movie': movie,
|
||||
}
|
||||
|
||||
def get(self, movie_id):
|
||||
|
||||
db = get_session()
|
||||
|
||||
imdb_id = getImdb(str(movie_id))
|
||||
|
||||
if(imdb_id):
|
||||
m = db.query(Media).filter(Media.library.has(identifier = imdb_id)).first()
|
||||
else:
|
||||
m = db.query(Media).filter_by(id = movie_id).first()
|
||||
|
||||
results = None
|
||||
if m:
|
||||
results = m.to_dict(self.default_dict)
|
||||
|
||||
db.expire_all()
|
||||
return results
|
||||
|
||||
def list(self, status = None, release_status = None, limit_offset = None, starts_with = None, search = None, order = None):
|
||||
|
||||
db = get_session()
|
||||
|
||||
# Make a list from string
|
||||
if status and not isinstance(status, (list, tuple)):
|
||||
status = [status]
|
||||
if release_status and not isinstance(release_status, (list, tuple)):
|
||||
release_status = [release_status]
|
||||
|
||||
# query movie ids
|
||||
q = db.query(Media) \
|
||||
.with_entities(Media.id) \
|
||||
.group_by(Media.id)
|
||||
|
||||
# Filter on movie status
|
||||
if status and len(status) > 0:
|
||||
statuses = fireEvent('status.get', status, single = len(status) > 1)
|
||||
statuses = [s.get('id') for s in statuses]
|
||||
|
||||
q = q.filter(Media.status_id.in_(statuses))
|
||||
|
||||
# Filter on release status
|
||||
if release_status and len(release_status) > 0:
|
||||
q = q.join(Media.releases)
|
||||
|
||||
statuses = fireEvent('status.get', release_status, single = len(release_status) > 1)
|
||||
statuses = [s.get('id') for s in statuses]
|
||||
|
||||
q = q.filter(Release.status_id.in_(statuses))
|
||||
|
||||
# Only join when searching / ordering
|
||||
if starts_with or search or order != 'release_order':
|
||||
q = q.join(Media.library, Library.titles) \
|
||||
.filter(LibraryTitle.default == True)
|
||||
|
||||
# Add search filters
|
||||
filter_or = []
|
||||
if starts_with:
|
||||
starts_with = toUnicode(starts_with.lower())
|
||||
if starts_with in ascii_lowercase:
|
||||
filter_or.append(LibraryTitle.simple_title.startswith(starts_with))
|
||||
else:
|
||||
ignore = []
|
||||
for letter in ascii_lowercase:
|
||||
ignore.append(LibraryTitle.simple_title.startswith(toUnicode(letter)))
|
||||
filter_or.append(not_(or_(*ignore)))
|
||||
|
||||
if search:
|
||||
filter_or.append(LibraryTitle.simple_title.like('%%' + search + '%%'))
|
||||
|
||||
if len(filter_or) > 0:
|
||||
q = q.filter(or_(*filter_or))
|
||||
|
||||
total_count = q.count()
|
||||
if total_count == 0:
|
||||
return 0, []
|
||||
|
||||
if order == 'release_order':
|
||||
q = q.order_by(desc(Release.last_edit))
|
||||
else:
|
||||
q = q.order_by(asc(LibraryTitle.simple_title))
|
||||
|
||||
if limit_offset:
|
||||
splt = splitString(limit_offset) if isinstance(limit_offset, (str, unicode)) else limit_offset
|
||||
limit = splt[0]
|
||||
offset = 0 if len(splt) is 1 else splt[1]
|
||||
q = q.limit(limit).offset(offset)
|
||||
|
||||
# Get all movie_ids in sorted order
|
||||
movie_ids = [m.id for m in q.all()]
|
||||
|
||||
# List release statuses
|
||||
releases = db.query(Release) \
|
||||
.filter(Release.media_id.in_(movie_ids)) \
|
||||
.all()
|
||||
|
||||
release_statuses = dict((m, set()) for m in movie_ids)
|
||||
releases_count = dict((m, 0) for m in movie_ids)
|
||||
for release in releases:
|
||||
release_statuses[release.media_id].add('%d,%d' % (release.status_id, release.quality_id))
|
||||
releases_count[release.media_id] += 1
|
||||
|
||||
# Get main movie data
|
||||
q2 = db.query(Media) \
|
||||
.options(joinedload_all('library.titles')) \
|
||||
.options(joinedload_all('library.files')) \
|
||||
.options(joinedload_all('status')) \
|
||||
.options(joinedload_all('files'))
|
||||
|
||||
q2 = q2.filter(Media.id.in_(movie_ids))
|
||||
|
||||
results = q2.all()
|
||||
|
||||
# Create dict by movie id
|
||||
movie_dict = {}
|
||||
for movie in results:
|
||||
movie_dict[movie.id] = movie
|
||||
|
||||
# List movies based on movie_ids order
|
||||
movies = []
|
||||
for movie_id in movie_ids:
|
||||
|
||||
releases = []
|
||||
for r in release_statuses.get(movie_id):
|
||||
x = splitString(r)
|
||||
releases.append({'status_id': x[0], 'quality_id': x[1]})
|
||||
|
||||
# Merge releases with movie dict
|
||||
movies.append(mergeDicts(movie_dict[movie_id].to_dict({
|
||||
'library': {'titles': {}, 'files':{}},
|
||||
'files': {},
|
||||
}), {
|
||||
'releases': releases,
|
||||
'releases_count': releases_count.get(movie_id),
|
||||
}))
|
||||
|
||||
db.expire_all()
|
||||
return total_count, movies
|
||||
|
||||
def availableChars(self, status = None, release_status = None):
|
||||
|
||||
status = status or []
|
||||
release_status = release_status or []
|
||||
|
||||
db = get_session()
|
||||
|
||||
# Make a list from string
|
||||
if not isinstance(status, (list, tuple)):
|
||||
status = [status]
|
||||
if release_status and not isinstance(release_status, (list, tuple)):
|
||||
release_status = [release_status]
|
||||
|
||||
q = db.query(Media)
|
||||
|
||||
# Filter on movie status
|
||||
if status and len(status) > 0:
|
||||
statuses = fireEvent('status.get', status, single = len(release_status) > 1)
|
||||
statuses = [s.get('id') for s in statuses]
|
||||
|
||||
q = q.filter(Media.status_id.in_(statuses))
|
||||
|
||||
# Filter on release status
|
||||
if release_status and len(release_status) > 0:
|
||||
|
||||
statuses = fireEvent('status.get', release_status, single = len(release_status) > 1)
|
||||
statuses = [s.get('id') for s in statuses]
|
||||
|
||||
q = q.join(Media.releases) \
|
||||
.filter(Release.status_id.in_(statuses))
|
||||
|
||||
q = q.join(Library, LibraryTitle) \
|
||||
.with_entities(LibraryTitle.simple_title) \
|
||||
.filter(LibraryTitle.default == True)
|
||||
|
||||
titles = q.all()
|
||||
|
||||
chars = set()
|
||||
for title in titles:
|
||||
try:
|
||||
char = title[0][0]
|
||||
char = char if char in ascii_lowercase else '#'
|
||||
chars.add(str(char))
|
||||
except:
|
||||
log.error('Failed getting title for %s', title.libraries_id)
|
||||
|
||||
if len(chars) == 25:
|
||||
break
|
||||
|
||||
db.expire_all()
|
||||
return ''.join(sorted(chars))
|
||||
|
||||
def listView(self, **kwargs):
|
||||
|
||||
status = splitString(kwargs.get('status'))
|
||||
release_status = splitString(kwargs.get('release_status'))
|
||||
limit_offset = kwargs.get('limit_offset')
|
||||
starts_with = kwargs.get('starts_with')
|
||||
search = kwargs.get('search')
|
||||
order = kwargs.get('order')
|
||||
|
||||
total_movies, movies = self.list(
|
||||
status = status,
|
||||
release_status = release_status,
|
||||
limit_offset = limit_offset,
|
||||
starts_with = starts_with,
|
||||
search = search,
|
||||
order = order
|
||||
)
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'empty': len(movies) == 0,
|
||||
'total': total_movies,
|
||||
'movies': movies,
|
||||
}
|
||||
|
||||
def charView(self, **kwargs):
|
||||
|
||||
status = splitString(kwargs.get('status', None))
|
||||
release_status = splitString(kwargs.get('release_status', None))
|
||||
chars = self.availableChars(status, release_status)
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'empty': len(chars) == 0,
|
||||
'chars': chars,
|
||||
}
|
||||
|
||||
def add(self, params = None, force_readd = True, search_after = True, update_library = False, status_id = None):
|
||||
if not params: params = {}
|
||||
@@ -447,7 +173,7 @@ class MovieBase(MovieTypeBase):
|
||||
|
||||
db.commit()
|
||||
|
||||
fireEvent('movie.restatus', m.id)
|
||||
fireEvent('media.restatus', m.id)
|
||||
|
||||
movie_dict = m.to_dict(self.default_dict)
|
||||
fireEventAsync('movie.searcher.single', movie_dict, on_complete = self.createNotifyFront(movie_id))
|
||||
@@ -456,89 +182,3 @@ class MovieBase(MovieTypeBase):
|
||||
return {
|
||||
'success': True,
|
||||
}
|
||||
|
||||
def deleteView(self, id = '', **kwargs):
|
||||
|
||||
ids = splitString(id)
|
||||
for movie_id in ids:
|
||||
self.delete(movie_id, delete_from = kwargs.get('delete_from', 'all'))
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
}
|
||||
|
||||
def delete(self, movie_id, delete_from = None):
|
||||
|
||||
db = get_session()
|
||||
|
||||
movie = db.query(Media).filter_by(id = movie_id).first()
|
||||
if movie:
|
||||
deleted = False
|
||||
if delete_from == 'all':
|
||||
db.delete(movie)
|
||||
db.commit()
|
||||
deleted = True
|
||||
else:
|
||||
done_status = fireEvent('status.get', 'done', single = True)
|
||||
|
||||
total_releases = len(movie.releases)
|
||||
total_deleted = 0
|
||||
new_movie_status = None
|
||||
for release in movie.releases:
|
||||
if delete_from in ['wanted', 'snatched', 'late']:
|
||||
if release.status_id != done_status.get('id'):
|
||||
db.delete(release)
|
||||
total_deleted += 1
|
||||
new_movie_status = 'done'
|
||||
elif delete_from == 'manage':
|
||||
if release.status_id == done_status.get('id'):
|
||||
db.delete(release)
|
||||
total_deleted += 1
|
||||
new_movie_status = 'active'
|
||||
db.commit()
|
||||
|
||||
if total_releases == total_deleted:
|
||||
db.delete(movie)
|
||||
db.commit()
|
||||
deleted = True
|
||||
elif new_movie_status:
|
||||
new_status = fireEvent('status.get', new_movie_status, single = True)
|
||||
movie.profile_id = None
|
||||
movie.status_id = new_status.get('id')
|
||||
db.commit()
|
||||
else:
|
||||
fireEvent('movie.restatus', movie.id, single = True)
|
||||
|
||||
if deleted:
|
||||
fireEvent('notify.frontend', type = 'movie.deleted', data = movie.to_dict())
|
||||
|
||||
db.expire_all()
|
||||
return True
|
||||
|
||||
def restatus(self, movie_id):
|
||||
|
||||
active_status, done_status = fireEvent('status.get', ['active', 'done'], single = True)
|
||||
|
||||
db = get_session()
|
||||
|
||||
m = db.query(Media).filter_by(id = movie_id).first()
|
||||
if not m or len(m.library.titles) == 0:
|
||||
log.debug('Can\'t restatus movie, doesn\'t seem to exist.')
|
||||
return False
|
||||
|
||||
log.debug('Changing status for %s', m.library.titles[0].title)
|
||||
if not m.profile:
|
||||
m.status_id = done_status.get('id')
|
||||
else:
|
||||
move_to_wanted = True
|
||||
|
||||
for t in m.profile.types:
|
||||
for release in m.releases:
|
||||
if t.quality.identifier is release.quality.identifier and (release.status_id is done_status.get('id') and t.finish):
|
||||
move_to_wanted = False
|
||||
|
||||
m.status_id = active_status.get('id') if move_to_wanted else done_status.get('id')
|
||||
|
||||
db.commit()
|
||||
|
||||
return True
|
||||
|
||||
@@ -281,7 +281,7 @@ var MovieList = new Class({
|
||||
|
||||
// Get available chars and highlight
|
||||
if(!available_chars && (self.navigation.isDisplayed() || self.navigation.isVisible()))
|
||||
Api.request('movie.available_chars', {
|
||||
Api.request('media.available_chars', {
|
||||
'data': Object.merge({
|
||||
'status': self.options.status
|
||||
}, self.filter),
|
||||
@@ -372,7 +372,7 @@ var MovieList = new Class({
|
||||
'click': function(e){
|
||||
(e).preventDefault();
|
||||
this.set('text', 'Deleting..')
|
||||
Api.request('movie.delete', {
|
||||
Api.request('media.delete', {
|
||||
'data': {
|
||||
'id': ids.join(','),
|
||||
'delete_from': self.options.identifier
|
||||
@@ -550,8 +550,9 @@ var MovieList = new Class({
|
||||
|
||||
}
|
||||
|
||||
Api.request(self.options.api_call || 'movie.list', {
|
||||
Api.request(self.options.api_call || 'media.list', {
|
||||
'data': Object.merge({
|
||||
'type': 'movie',
|
||||
'status': self.options.status,
|
||||
'limit_offset': self.options.limit ? self.options.limit + ',' + self.offset : null
|
||||
}, self.filter),
|
||||
|
||||
@@ -431,7 +431,7 @@ MA.Release = new Class({
|
||||
markMovieDone: function(){
|
||||
var self = this;
|
||||
|
||||
Api.request('movie.delete', {
|
||||
Api.request('media.delete', {
|
||||
'data': {
|
||||
'id': self.movie.get('id'),
|
||||
'delete_from': 'wanted'
|
||||
@@ -821,7 +821,7 @@ MA.Delete = new Class({
|
||||
self.callChain();
|
||||
},
|
||||
function(){
|
||||
Api.request('movie.delete', {
|
||||
Api.request('media.delete', {
|
||||
'data': {
|
||||
'id': self.movie.get('id'),
|
||||
'delete_from': self.movie.list.options.identifier
|
||||
|
||||
@@ -78,9 +78,9 @@ var Movie = new Class({
|
||||
self.list.checkIfEmpty();
|
||||
|
||||
// Remove events
|
||||
self.global_events.each(function(handle, listener){
|
||||
Object.each(self.global_events, function(handle, listener){
|
||||
App.off(listener, handle);
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
busy: function(set_busy, timeout){
|
||||
|
||||
@@ -145,7 +145,7 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
|
||||
default_title = getTitle(movie['library'])
|
||||
if not default_title:
|
||||
log.error('No proper info found for movie, removing it from library to cause it from having more issues.')
|
||||
fireEvent('movie.delete', movie['id'], single = True)
|
||||
fireEvent('media.delete', movie['id'], single = True)
|
||||
return
|
||||
|
||||
fireEvent('notify.frontend', type = 'movie.searcher.started', data = {'id': movie['id']}, message = 'Searching for "%s"' % default_title)
|
||||
@@ -192,7 +192,7 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
|
||||
|
||||
else:
|
||||
log.info('Better quality (%s) already available or snatched for %s', (quality_type['quality']['label'], default_title))
|
||||
fireEvent('movie.restatus', movie['id'])
|
||||
fireEvent('media.restatus', movie['id'])
|
||||
break
|
||||
|
||||
# Break if CP wants to shut down
|
||||
@@ -333,7 +333,7 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
|
||||
rel.status_id = ignored_status.get('id')
|
||||
db.commit()
|
||||
|
||||
movie_dict = fireEvent('movie.get', movie_id, single = True)
|
||||
movie_dict = fireEvent('media.get', movie_id, single = True)
|
||||
log.info('Trying next release for: %s', getTitle(movie_dict['library']))
|
||||
fireEvent('movie.searcher.single', movie_dict, manual = manual)
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ config = [{
|
||||
'label': 'Required Genres',
|
||||
'default': '',
|
||||
'placeholder': 'Example: Action, Crime & Drama',
|
||||
'description': 'Ignore movies that don\'t contain at least one set of genres. Sets are separated by "," and each word within a set must be separated with "&"'
|
||||
'description': ('Ignore movies that don\'t contain at least one set of genres.', 'Sets are separated by "," and each word within a set must be separated with "&"')
|
||||
},
|
||||
{
|
||||
'name': 'ignored_genres',
|
||||
|
||||
@@ -43,7 +43,7 @@ class Automation(Plugin):
|
||||
if self.shuttingDown():
|
||||
break
|
||||
|
||||
movie_dict = fireEvent('movie.get', movie_id, single = True)
|
||||
movie_dict = fireEvent('media.get', movie_id, single = True)
|
||||
fireEvent('movie.searcher.single', movie_dict)
|
||||
|
||||
return True
|
||||
return True
|
||||
|
||||
@@ -289,19 +289,19 @@ class Plugin(object):
|
||||
Env.get('cache').set(cache_key_md5, value, timeout)
|
||||
return value
|
||||
|
||||
def createNzbName(self, data, movie):
|
||||
tag = self.cpTag(movie)
|
||||
def createNzbName(self, data, media):
|
||||
tag = self.cpTag(media)
|
||||
return '%s%s' % (toSafeString(toUnicode(data.get('name'))[:127 - len(tag)]), tag)
|
||||
|
||||
def createFileName(self, data, filedata, movie):
|
||||
name = sp(os.path.join(self.createNzbName(data, movie)))
|
||||
def createFileName(self, data, filedata, media):
|
||||
name = sp(os.path.join(self.createNzbName(data, media)))
|
||||
if data.get('protocol') == 'nzb' and 'DOCTYPE nzb' not in filedata and '</nzb>' not in filedata:
|
||||
return '%s.%s' % (name, 'rar')
|
||||
return '%s.%s' % (name, data.get('protocol'))
|
||||
|
||||
def cpTag(self, movie):
|
||||
def cpTag(self, media):
|
||||
if Env.setting('enabled', 'renamer'):
|
||||
return '.cp(' + movie['library'].get('identifier') + ')' if movie['library'].get('identifier') else ''
|
||||
return '.cp(' + media['library'].get('identifier') + ')' if media['library'].get('identifier') else ''
|
||||
|
||||
return ''
|
||||
|
||||
|
||||
@@ -112,11 +112,11 @@ class Manage(Plugin):
|
||||
if self.conf('cleanup') and full and not self.shuttingDown():
|
||||
|
||||
# Get movies with done status
|
||||
total_movies, done_movies = fireEvent('movie.list', status = 'done', single = True)
|
||||
total_movies, done_movies = fireEvent('media.list', types = 'movie', status = 'done', single = True)
|
||||
|
||||
for done_movie in done_movies:
|
||||
if done_movie['library']['identifier'] not in added_identifiers:
|
||||
fireEvent('movie.delete', movie_id = done_movie['id'], delete_from = 'all')
|
||||
fireEvent('media.delete', movie_id = done_movie['id'], delete_from = 'all')
|
||||
else:
|
||||
|
||||
releases = fireEvent('release.for_movie', id = done_movie.get('id'), single = True)
|
||||
@@ -202,7 +202,7 @@ class Manage(Plugin):
|
||||
|
||||
self.in_progress[folder]['to_go'] -= 1
|
||||
total = self.in_progress[folder]['total']
|
||||
movie_dict = fireEvent('movie.get', identifier, single = True)
|
||||
movie_dict = fireEvent('media.get', identifier, single = True)
|
||||
|
||||
fireEvent('notify.frontend', type = 'movie.added', data = movie_dict, message = None if total > 5 else 'Added "%s" to manage.' % getTitle(movie_dict['library']))
|
||||
|
||||
|
||||
@@ -142,7 +142,7 @@ class Release(Plugin):
|
||||
except:
|
||||
log.debug('Failed to attach "%s" to release: %s', (added_files, traceback.format_exc()))
|
||||
|
||||
fireEvent('movie.restatus', media.id)
|
||||
fireEvent('media.restatus', media.id)
|
||||
|
||||
return True
|
||||
|
||||
@@ -269,7 +269,7 @@ class Release(Plugin):
|
||||
if filedata == 'try_next':
|
||||
return filedata
|
||||
|
||||
download_result = fireEvent('download', data = data, movie = media, manual = manual, filedata = filedata, single = True)
|
||||
download_result = fireEvent('download', data = data, media = media, manual = manual, filedata = filedata, single = True)
|
||||
log.debug('Downloader result: %s', download_result)
|
||||
|
||||
if download_result:
|
||||
|
||||
@@ -93,7 +93,7 @@ config = [{
|
||||
'default': 1,
|
||||
'type': 'int',
|
||||
'unit': 'min(s)',
|
||||
'description': 'Detect movie status every X minutes. Will start the renamer if movie is <strong>completed</strong> or handle <strong>failed</strong> download if these options are enabled',
|
||||
'description': ('Detect movie status every X minutes.', 'Will start the renamer if movie is <strong>completed</strong> or handle <strong>failed</strong> download if these options are enabled'),
|
||||
},
|
||||
{
|
||||
'advanced': True,
|
||||
@@ -122,13 +122,13 @@ config = [{
|
||||
'advanced': True,
|
||||
'name': 'separator',
|
||||
'label': 'File-Separator',
|
||||
'description': 'Replace all the spaces with a character. Example: ".", "-" (without quotes). Leave empty to use spaces.',
|
||||
'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.',
|
||||
'description': ('Replace all the spaces with a character.', 'Example: ".", "-" (without quotes). Leave empty to use spaces.'),
|
||||
},
|
||||
{
|
||||
'name': 'file_action',
|
||||
@@ -136,7 +136,7 @@ config = [{
|
||||
'default': 'link',
|
||||
'type': 'dropdown',
|
||||
'values': [('Link', 'link'), ('Copy', 'copy'), ('Move', 'move')],
|
||||
'description': '<strong>Link</strong> or <strong>Copy</strong> after downloading completed (and allow for seeding), or <strong>Move</strong> after seeding completed. Link first tries <a href="http://en.wikipedia.org/wiki/Hard_link">hard link</a>, then <a href="http://en.wikipedia.org/wiki/Sym_link">sym link</a> and falls back to Copy.',
|
||||
'description': ('<strong>Link</strong>, <strong>Copy</strong> or <strong>Move</strong> after download completed.', 'Link first tries <a href="http://en.wikipedia.org/wiki/Hard_link">hard link</a>, then <a href="http://en.wikipedia.org/wiki/Sym_link">sym link</a> 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.'),
|
||||
'advanced': True,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -820,6 +820,7 @@ Remove it if you want it to be renamed (again, or at least let it try again)
|
||||
try:
|
||||
for rel in rels:
|
||||
rel_dict = rel.to_dict({'info': {}})
|
||||
movie_dict = fireEvent('media.get', rel.movie_id, single = True)
|
||||
|
||||
if not isinstance(rel_dict['info'], (dict)):
|
||||
log.error('Faulty release found without any info, ignoring.')
|
||||
|
||||
@@ -20,7 +20,7 @@ config = [{
|
||||
},
|
||||
{
|
||||
'name': 'languages',
|
||||
'description': 'Comma separated, 2 letter country code. Example: en, nl. See the codes at <a href="http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes">on Wikipedia</a>',
|
||||
'description': ('Comma separated, 2 letter country code.', 'Example: en, nl. See the codes at <a href="http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes">on Wikipedia</a>'),
|
||||
},
|
||||
# {
|
||||
# 'name': 'automatic',
|
||||
|
||||
@@ -290,14 +290,14 @@ class ResultList(list):
|
||||
|
||||
result_ids = None
|
||||
provider = None
|
||||
movie = None
|
||||
media = None
|
||||
quality = None
|
||||
|
||||
def __init__(self, provider, movie, quality, **kwargs):
|
||||
def __init__(self, provider, media, quality, **kwargs):
|
||||
|
||||
self.result_ids = []
|
||||
self.provider = provider
|
||||
self.movie = movie
|
||||
self.media = media
|
||||
self.quality = quality
|
||||
self.kwargs = kwargs
|
||||
|
||||
@@ -311,13 +311,13 @@ class ResultList(list):
|
||||
|
||||
new_result = self.fillResult(result)
|
||||
|
||||
is_correct = fireEvent('searcher.correct_release', new_result, self.movie, self.quality,
|
||||
is_correct = fireEvent('searcher.correct_release', new_result, self.media, self.quality,
|
||||
imdb_results = self.kwargs.get('imdb_results', False), single = True)
|
||||
|
||||
if is_correct and new_result['id'] not in self.result_ids:
|
||||
is_correct_weight = float(is_correct)
|
||||
|
||||
new_result['score'] += fireEvent('score.calculate', new_result, self.movie, single = True)
|
||||
new_result['score'] += fireEvent('score.calculate', new_result, self.media, single = True)
|
||||
|
||||
old_score = new_result['score']
|
||||
new_result['score'] = int(old_score * is_correct_weight)
|
||||
|
||||
@@ -104,11 +104,11 @@ class Movie(ModifierBase):
|
||||
|
||||
for movie in l.media:
|
||||
if movie.status_id == active_status['id']:
|
||||
temp['in_wanted'] = fireEvent('movie.get', movie.id, single = True)
|
||||
temp['in_wanted'] = fireEvent('media.get', movie.id, single = True)
|
||||
|
||||
for release in movie.releases:
|
||||
if release.status_id == done_status['id']:
|
||||
temp['in_library'] = fireEvent('movie.get', movie.id, single = True)
|
||||
temp['in_library'] = fireEvent('media.get', movie.id, single = True)
|
||||
except:
|
||||
log.error('Tried getting more info on searched movies: %s', traceback.format_exc())
|
||||
|
||||
|
||||
66
couchpotato/core/providers/torrent/torrentpotato/__init__.py
Normal file
66
couchpotato/core/providers/torrent/torrentpotato/__init__.py
Normal file
@@ -0,0 +1,66 @@
|
||||
from .main import TorrentPotato
|
||||
|
||||
def start():
|
||||
return TorrentPotato()
|
||||
|
||||
config = [{
|
||||
'name': 'torrentpotato',
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'searcher',
|
||||
'list': 'torrent_providers',
|
||||
'name': 'TorrentPotato',
|
||||
'order': 10,
|
||||
'description': 'CouchPotato torrent provider. Checkout <a href="https://github.com/RuudBurger/CouchPotatoServer/wiki/CouchPotato-Torrent-Provider">the wiki page about this provider</a> for more info.',
|
||||
'wizard': True,
|
||||
'options': [
|
||||
{
|
||||
'name': 'enabled',
|
||||
'type': 'enabler',
|
||||
'default': False,
|
||||
},
|
||||
{
|
||||
'name': 'use',
|
||||
'default': ''
|
||||
},
|
||||
{
|
||||
'name': 'host',
|
||||
'default': '',
|
||||
'description': 'The url path of your TorrentPotato provider.',
|
||||
},
|
||||
{
|
||||
'name': 'extra_score',
|
||||
'advanced': True,
|
||||
'label': 'Extra Score',
|
||||
'default': '0',
|
||||
'description': 'Starting score for each release found via this provider.',
|
||||
},
|
||||
{
|
||||
'name': 'name',
|
||||
'label': 'Username',
|
||||
'default': '',
|
||||
},
|
||||
{
|
||||
'name': 'seed_ratio',
|
||||
'label': 'Seed ratio',
|
||||
'default': '1',
|
||||
'description': 'Will not be (re)moved until this seed ratio is met.',
|
||||
},
|
||||
{
|
||||
'name': 'seed_time',
|
||||
'label': 'Seed time',
|
||||
'default': '40',
|
||||
'description': 'Will not be (re)moved until this seed time (in hours) is met.',
|
||||
},
|
||||
{
|
||||
'name': 'pass_key',
|
||||
'default': ',',
|
||||
'label': 'Pass Key',
|
||||
'description': 'Can be found on your profile page',
|
||||
'type': 'combined',
|
||||
'combine': ['use', 'host', 'pass_key', 'name', 'seed_ratio', 'seed_time', 'extra_score'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}]
|
||||
129
couchpotato/core/providers/torrent/torrentpotato/main.py
Normal file
129
couchpotato/core/providers/torrent/torrentpotato/main.py
Normal file
@@ -0,0 +1,129 @@
|
||||
from couchpotato.core.helpers.encoding import tryUrlencode, toUnicode
|
||||
from couchpotato.core.helpers.variable import splitString, tryInt, tryFloat
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.providers.base import ResultList
|
||||
from couchpotato.core.providers.torrent.base import TorrentProvider
|
||||
from urlparse import urlparse
|
||||
import re
|
||||
import traceback
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
class TorrentPotato(TorrentProvider):
|
||||
|
||||
urls = {}
|
||||
limits_reached = {}
|
||||
|
||||
http_time_between_calls = 1 # Seconds
|
||||
|
||||
def search(self, movie, quality):
|
||||
hosts = self.getHosts()
|
||||
|
||||
results = ResultList(self, movie, quality, imdb_results = True)
|
||||
|
||||
for host in hosts:
|
||||
if self.isDisabled(host):
|
||||
continue
|
||||
|
||||
self._searchOnHost(host, movie, quality, results)
|
||||
|
||||
return results
|
||||
|
||||
def _searchOnHost(self, host, movie, quality, results):
|
||||
|
||||
arguments = tryUrlencode({
|
||||
'user': host['name'],
|
||||
'passkey': host['pass_key'],
|
||||
'imdbid': movie['library']['identifier']
|
||||
})
|
||||
url = '%s?%s' % (host['host'], arguments)
|
||||
|
||||
torrents = self.getJsonData(url, cache_timeout = 1800)
|
||||
|
||||
if torrents:
|
||||
try:
|
||||
if torrents.get('error'):
|
||||
log.error('%s: %s', (torrents.get('error'), host['host']))
|
||||
elif torrents.get('results'):
|
||||
for torrent in torrents.get('results', []):
|
||||
results.append({
|
||||
'id': torrent.get('torrent_id'),
|
||||
'protocol': 'torrent' if re.match('^(http|https|ftp)://.*$', torrent.get('download_url')) else 'torrent_magnet',
|
||||
'provider_extra': urlparse(host['host']).hostname or host['host'],
|
||||
'name': toUnicode(torrent.get('release_name')),
|
||||
'url': torrent.get('download_url'),
|
||||
'detail_url': torrent.get('details_url'),
|
||||
'size': torrent.get('size'),
|
||||
'score': host['extra_score'],
|
||||
'seeders': torrent.get('seeders'),
|
||||
'leechers': torrent.get('leechers'),
|
||||
'seed_ratio': host['seed_ratio'],
|
||||
'seed_time': host['seed_time'],
|
||||
})
|
||||
|
||||
except:
|
||||
log.error('Failed getting results from %s: %s', (host['host'], traceback.format_exc()))
|
||||
|
||||
def getHosts(self):
|
||||
|
||||
uses = splitString(str(self.conf('use')), clean = False)
|
||||
hosts = splitString(self.conf('host'), clean = False)
|
||||
names = splitString(self.conf('name'), clean = False)
|
||||
seed_times = splitString(self.conf('seed_time'), clean = False)
|
||||
seed_ratios = splitString(self.conf('seed_ratio'), clean = False)
|
||||
pass_keys = splitString(self.conf('pass_key'), clean = False)
|
||||
extra_score = splitString(self.conf('extra_score'), clean = False)
|
||||
|
||||
list = []
|
||||
for nr in range(len(hosts)):
|
||||
|
||||
try: key = pass_keys[nr]
|
||||
except: key = ''
|
||||
|
||||
try: host = hosts[nr]
|
||||
except: host = ''
|
||||
|
||||
try: name = names[nr]
|
||||
except: name = ''
|
||||
|
||||
try: ratio = seed_ratios[nr]
|
||||
except: ratio = ''
|
||||
|
||||
try: seed_time = seed_times[nr]
|
||||
except: seed_time = ''
|
||||
|
||||
list.append({
|
||||
'use': uses[nr],
|
||||
'host': host,
|
||||
'name': name,
|
||||
'seed_ratio': tryFloat(ratio),
|
||||
'seed_time': tryInt(seed_time),
|
||||
'pass_key': key,
|
||||
'extra_score': tryInt(extra_score[nr]) if len(extra_score) > nr else 0
|
||||
})
|
||||
|
||||
return list
|
||||
|
||||
def belongsTo(self, url, provider = None, host = None):
|
||||
|
||||
hosts = self.getHosts()
|
||||
|
||||
for host in hosts:
|
||||
result = super(TorrentPotato, self).belongsTo(url, host = host['host'], provider = provider)
|
||||
if result:
|
||||
return result
|
||||
|
||||
def isDisabled(self, host = None):
|
||||
return not self.isEnabled(host)
|
||||
|
||||
def isEnabled(self, host = None):
|
||||
|
||||
# Return true if at least one is enabled and no host is given
|
||||
if host is None:
|
||||
for host in self.getHosts():
|
||||
if self.isEnabled(host):
|
||||
return True
|
||||
return False
|
||||
|
||||
return TorrentProvider.isEnabled(self) and host['host'] and host['pass_key'] and int(host['use'])
|
||||
@@ -1,6 +1,6 @@
|
||||
// MooTools: the javascript framework.
|
||||
// Load this file's selection again by visiting: http://mootools.net/more/43db227db7a621ebb062ee621432ae3d
|
||||
// Or build this file again with packager using: packager build More/Events.Pseudos More/Date More/Date.Extras More/Element.Forms More/Element.Position More/Element.Shortcuts More/Fx.Scroll More/Fx.Slide More/Sortables More/Request.JSONP More/Request.Periodical
|
||||
// Load this file's selection again by visiting: http://mootools.net/more/7a819726f7f5e85fc48bef295ff78dbe
|
||||
// Or build this file again with packager using: packager build More/Events.Pseudos More/Date More/Date.Extras More/Element.Forms More/Element.Position More/Element.Shortcuts More/Fx.Scroll More/Fx.Slide More/Sortables More/Request.JSONP More/Request.Periodical More/Tips
|
||||
/*
|
||||
---
|
||||
|
||||
@@ -3161,3 +3161,264 @@ Request.implement({
|
||||
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
---
|
||||
|
||||
script: Tips.js
|
||||
|
||||
name: Tips
|
||||
|
||||
description: Class for creating nice tips that follow the mouse cursor when hovering an element.
|
||||
|
||||
license: MIT-style license
|
||||
|
||||
authors:
|
||||
- Valerio Proietti
|
||||
- Christoph Pojer
|
||||
- Luis Merino
|
||||
|
||||
requires:
|
||||
- Core/Options
|
||||
- Core/Events
|
||||
- Core/Element.Event
|
||||
- Core/Element.Style
|
||||
- Core/Element.Dimensions
|
||||
- /MooTools.More
|
||||
|
||||
provides: [Tips]
|
||||
|
||||
...
|
||||
*/
|
||||
|
||||
(function(){
|
||||
|
||||
var read = function(option, element){
|
||||
return (option) ? (typeOf(option) == 'function' ? option(element) : element.get(option)) : '';
|
||||
};
|
||||
|
||||
this.Tips = new Class({
|
||||
|
||||
Implements: [Events, Options],
|
||||
|
||||
options: {/*
|
||||
id: null,
|
||||
onAttach: function(element){},
|
||||
onDetach: function(element){},
|
||||
onBound: function(coords){},*/
|
||||
onShow: function(){
|
||||
this.tip.setStyle('display', 'block');
|
||||
},
|
||||
onHide: function(){
|
||||
this.tip.setStyle('display', 'none');
|
||||
},
|
||||
title: 'title',
|
||||
text: function(element){
|
||||
return element.get('rel') || element.get('href');
|
||||
},
|
||||
showDelay: 100,
|
||||
hideDelay: 100,
|
||||
className: 'tip-wrap',
|
||||
offset: {x: 16, y: 16},
|
||||
windowPadding: {x:0, y:0},
|
||||
fixed: false,
|
||||
waiAria: true
|
||||
},
|
||||
|
||||
initialize: function(){
|
||||
var params = Array.link(arguments, {
|
||||
options: Type.isObject,
|
||||
elements: function(obj){
|
||||
return obj != null;
|
||||
}
|
||||
});
|
||||
this.setOptions(params.options);
|
||||
if (params.elements) this.attach(params.elements);
|
||||
this.container = new Element('div', {'class': 'tip'});
|
||||
|
||||
if (this.options.id){
|
||||
this.container.set('id', this.options.id);
|
||||
if (this.options.waiAria) this.attachWaiAria();
|
||||
}
|
||||
},
|
||||
|
||||
toElement: function(){
|
||||
if (this.tip) return this.tip;
|
||||
|
||||
this.tip = new Element('div', {
|
||||
'class': this.options.className,
|
||||
styles: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0
|
||||
}
|
||||
}).adopt(
|
||||
new Element('div', {'class': 'tip-top'}),
|
||||
this.container,
|
||||
new Element('div', {'class': 'tip-bottom'})
|
||||
);
|
||||
|
||||
return this.tip;
|
||||
},
|
||||
|
||||
attachWaiAria: function(){
|
||||
var id = this.options.id;
|
||||
this.container.set('role', 'tooltip');
|
||||
|
||||
if (!this.waiAria){
|
||||
this.waiAria = {
|
||||
show: function(element){
|
||||
if (id) element.set('aria-describedby', id);
|
||||
this.container.set('aria-hidden', 'false');
|
||||
},
|
||||
hide: function(element){
|
||||
if (id) element.erase('aria-describedby');
|
||||
this.container.set('aria-hidden', 'true');
|
||||
}
|
||||
};
|
||||
}
|
||||
this.addEvents(this.waiAria);
|
||||
},
|
||||
|
||||
detachWaiAria: function(){
|
||||
if (this.waiAria){
|
||||
this.container.erase('role');
|
||||
this.container.erase('aria-hidden');
|
||||
this.removeEvents(this.waiAria);
|
||||
}
|
||||
},
|
||||
|
||||
attach: function(elements){
|
||||
$$(elements).each(function(element){
|
||||
var title = read(this.options.title, element),
|
||||
text = read(this.options.text, element);
|
||||
|
||||
element.set('title', '').store('tip:native', title).retrieve('tip:title', title);
|
||||
element.retrieve('tip:text', text);
|
||||
this.fireEvent('attach', [element]);
|
||||
|
||||
var events = ['enter', 'leave'];
|
||||
if (!this.options.fixed) events.push('move');
|
||||
|
||||
events.each(function(value){
|
||||
var event = element.retrieve('tip:' + value);
|
||||
if (!event) event = function(event){
|
||||
this['element' + value.capitalize()].apply(this, [event, element]);
|
||||
}.bind(this);
|
||||
|
||||
element.store('tip:' + value, event).addEvent('mouse' + value, event);
|
||||
}, this);
|
||||
}, this);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
detach: function(elements){
|
||||
$$(elements).each(function(element){
|
||||
['enter', 'leave', 'move'].each(function(value){
|
||||
element.removeEvent('mouse' + value, element.retrieve('tip:' + value)).eliminate('tip:' + value);
|
||||
});
|
||||
|
||||
this.fireEvent('detach', [element]);
|
||||
|
||||
if (this.options.title == 'title'){ // This is necessary to check if we can revert the title
|
||||
var original = element.retrieve('tip:native');
|
||||
if (original) element.set('title', original);
|
||||
}
|
||||
}, this);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
elementEnter: function(event, element){
|
||||
clearTimeout(this.timer);
|
||||
this.timer = (function(){
|
||||
this.container.empty();
|
||||
|
||||
['title', 'text'].each(function(value){
|
||||
var content = element.retrieve('tip:' + value);
|
||||
var div = this['_' + value + 'Element'] = new Element('div', {
|
||||
'class': 'tip-' + value
|
||||
}).inject(this.container);
|
||||
if (content) this.fill(div, content);
|
||||
}, this);
|
||||
this.show(element);
|
||||
this.position((this.options.fixed) ? {page: element.getPosition()} : event);
|
||||
}).delay(this.options.showDelay, this);
|
||||
},
|
||||
|
||||
elementLeave: function(event, element){
|
||||
clearTimeout(this.timer);
|
||||
this.timer = this.hide.delay(this.options.hideDelay, this, element);
|
||||
this.fireForParent(event, element);
|
||||
},
|
||||
|
||||
setTitle: function(title){
|
||||
if (this._titleElement){
|
||||
this._titleElement.empty();
|
||||
this.fill(this._titleElement, title);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
setText: function(text){
|
||||
if (this._textElement){
|
||||
this._textElement.empty();
|
||||
this.fill(this._textElement, text);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
fireForParent: function(event, element){
|
||||
element = element.getParent();
|
||||
if (!element || element == document.body) return;
|
||||
if (element.retrieve('tip:enter')) element.fireEvent('mouseenter', event);
|
||||
else this.fireForParent(event, element);
|
||||
},
|
||||
|
||||
elementMove: function(event, element){
|
||||
this.position(event);
|
||||
},
|
||||
|
||||
position: function(event){
|
||||
if (!this.tip) document.id(this);
|
||||
|
||||
var size = window.getSize(), scroll = window.getScroll(),
|
||||
tip = {x: this.tip.offsetWidth, y: this.tip.offsetHeight},
|
||||
props = {x: 'left', y: 'top'},
|
||||
bounds = {y: false, x2: false, y2: false, x: false},
|
||||
obj = {};
|
||||
|
||||
for (var z in props){
|
||||
obj[props[z]] = event.page[z] + this.options.offset[z];
|
||||
if (obj[props[z]] < 0) bounds[z] = true;
|
||||
if ((obj[props[z]] + tip[z] - scroll[z]) > size[z] - this.options.windowPadding[z]){
|
||||
obj[props[z]] = event.page[z] - this.options.offset[z] - tip[z];
|
||||
bounds[z+'2'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.fireEvent('bound', bounds);
|
||||
this.tip.setStyles(obj);
|
||||
},
|
||||
|
||||
fill: function(element, contents){
|
||||
if (typeof contents == 'string') element.set('html', contents);
|
||||
else element.adopt(contents);
|
||||
},
|
||||
|
||||
show: function(element){
|
||||
if (!this.tip) document.id(this);
|
||||
if (!this.tip.getParent()) this.tip.inject(document.body);
|
||||
this.fireEvent('show', [this.tip, element]);
|
||||
},
|
||||
|
||||
hide: function(element){
|
||||
if (!this.tip) document.id(this);
|
||||
this.fireEvent('hide', [this.tip, element]);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
})();
|
||||
|
||||
|
||||
@@ -265,16 +265,37 @@ Page.Settings = new Class({
|
||||
},
|
||||
|
||||
createGroup: function(group){
|
||||
|
||||
if((typeOf(group.description) == 'array')){
|
||||
var hint = new Element('span.hint.more_hint', {
|
||||
'html': group.description[0],
|
||||
'title': group.description[1]
|
||||
});
|
||||
var tip = new Tips(hint, {
|
||||
'fixed': true,
|
||||
'offset': {'x': 0, 'y': 0},
|
||||
'onShow': function(tip, hint){
|
||||
tip.setStyles({
|
||||
'margin-top': hint.getSize().y,
|
||||
'visibility': 'hidden',
|
||||
'display': 'block'
|
||||
}).fade('in');
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
var hint = new Element('span.hint', {
|
||||
'html': group.description || ''
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
return new Element('fieldset', {
|
||||
'class': (group.advanced ? 'inlineLabels advanced' : 'inlineLabels') + ' group_' + (group.name || '') + ' subtab_' + (group.subtab || '')
|
||||
}).adopt(
|
||||
}).grab(
|
||||
new Element('h2', {
|
||||
'text': group.label || (group.name).capitalize()
|
||||
}).adopt(
|
||||
new Element('span.hint', {
|
||||
'html': group.description || ''
|
||||
})
|
||||
)
|
||||
}).grab(hint)
|
||||
);
|
||||
},
|
||||
|
||||
@@ -343,10 +364,33 @@ var OptionBase = new Class({
|
||||
|
||||
createHint: function(){
|
||||
var self = this;
|
||||
if(self.options.description)
|
||||
new Element('p.formHint', {
|
||||
'html': self.options.description
|
||||
}).inject(self.el);
|
||||
if(self.options.description){
|
||||
|
||||
|
||||
if((typeOf(self.options.description) == 'array')){
|
||||
var hint = new Element('p.formHint.more_hint', {
|
||||
'html': self.options.description[0],
|
||||
'title': self.options.description[1]
|
||||
}).inject(self.el);
|
||||
var tip = new Tips(hint, {
|
||||
'fixed': true,
|
||||
'offset': {'x': 0, 'y': 0},
|
||||
'onShow': function(tip, hint){
|
||||
tip.setStyles({
|
||||
'margin-left': 13,
|
||||
'margin-top': hint.getSize().y+3,
|
||||
'visibility': 'hidden',
|
||||
'display': 'block'
|
||||
}).fade('in');
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
var hint = new Element('p.formHint', {
|
||||
'html': self.options.description || ''
|
||||
}).inject(self.el)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
afterInject: function(){
|
||||
|
||||
@@ -545,12 +545,31 @@
|
||||
.page .combined_table .head abbr:first-child {
|
||||
display: none;
|
||||
}
|
||||
.page .combined_table .head abbr.host {
|
||||
margin-right: 190px;
|
||||
.page .combined_table .head abbr.host { margin-right: 120px; }
|
||||
.page .combined_table input.host { width: 140px; }
|
||||
.page .section_newznab .combined_table .head abbr.host { margin-right: 200px; }
|
||||
.page .section_newznab .combined_table input.host { width: 220px; }
|
||||
|
||||
.page .combined_table .head abbr.name { margin-right: 57px; }
|
||||
.page .combined_table input.name { width: 120px; }
|
||||
.page .combined_table .head abbr.api_key { margin-right: 75px; }
|
||||
|
||||
.page .combined_table .head abbr.pass_key { margin-right: 71px; }
|
||||
.page .combined_table input.pass_key { width: 113px; }
|
||||
|
||||
.page .section_newznab .combined_table .head abbr.api_key { margin-right: 185px; }
|
||||
.page .section_newznab .combined_table input.api_key { width: 223px; }
|
||||
|
||||
.page .combined_table .seed_ratio,
|
||||
.page .combined_table .seed_time {
|
||||
width: 70px;
|
||||
text-align: center;
|
||||
margin-left: 10px;
|
||||
}
|
||||
.page .combined_table .head abbr.api_key {
|
||||
margin-right: 171px;
|
||||
.page .combined_table .seed_time {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.page .combined_table .head .extra_score,
|
||||
.page .combined_table .extra_score {
|
||||
width: 70px;
|
||||
@@ -699,4 +718,20 @@
|
||||
.active .group_imdb_automation:not(.disabled) {
|
||||
background: url('../images/imdb_watchlist.png') no-repeat right 50px;
|
||||
min-height: 210px;
|
||||
}
|
||||
}
|
||||
|
||||
.tip-wrap {
|
||||
background: #FFF;
|
||||
color: #000;
|
||||
padding: 10px;
|
||||
width: 300px;
|
||||
z-index: 200;
|
||||
}
|
||||
.more_hint:after {
|
||||
position: relative;
|
||||
font-family: 'Elusive-Icons';
|
||||
content: "\e089";
|
||||
display: inline-block;
|
||||
top: 1px;
|
||||
left: 6px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user