Merge branch 'refs/heads/develop'

This commit is contained in:
Ruud
2012-08-26 23:09:51 +02:00
48 changed files with 704 additions and 167 deletions

View File

@@ -9,7 +9,7 @@ import socket
import subprocess
import sys
import traceback
import time
# Root path
base_path = dirname(os.path.abspath(__file__))
@@ -96,6 +96,10 @@ class Loader(object):
except:
self.log.critical(traceback.format_exc())
# Release log files and shutdown logger
logging.shutdown()
time.sleep(3)
args = [sys.executable] + [os.path.join(base_path, __file__)] + sys.argv[1:]
subprocess.Popen(args)
except:

View File

@@ -7,11 +7,12 @@ Once a movie is found, it will send it to SABnzbd or download the torrent to a s
## Running from Source
CouchPotatoServer can be run from source. This will use *git* as updater, so make sure that is installed also.
CouchPotatoServer can be run from source. This will use *git* as updater, so make sure that is installed also.
Windows:
Windows, see [the CP forum](http://couchpota.to/forum/showthread.php?tid=14) for more details:
* Install [PyWin32 2.7](http://sourceforge.net/projects/pywin32/files/pywin32/Build%20217/) and [GIT](http://git-scm.com/)
* Install [Python 2.7](http://www.python.org/download/releases/2.7.3/)
* Then install [PyWin32 2.7](http://sourceforge.net/projects/pywin32/files/pywin32/Build%20217/) and [GIT](http://git-scm.com/)
* If you come and ask on the forums 'why directory selection no work?', I will kill a kitten, also this is because you need PyWin32
* Open up `Git Bash` (or CMD) and go to the folder you want to install CP. Something like Program Files.
* Run `git clone https://github.com/RuudBurger/CouchPotatoServer.git`.

View File

@@ -4,22 +4,28 @@ from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin
from couchpotato.environment import Env
import os
import re
import traceback
log = CPLog(__name__)
class Downloader(Plugin):
type = []
def __init__(self):
addEvent('download', self.download)
addEvent('download.status', self.getDownloadStatus)
def download(self, data = {}):
def download(self, data = {}, movie = {}, manual = False, filedata = None):
pass
def getDownloadStatus(self, data = {}, movie = {}):
pass
def createNzbName(self, data, movie):
return '%s%s' % (toSafeString(data.get('name')), self.cpTag(movie))
tag = self.cpTag(movie)
return '%s%s' % (toSafeString(data.get('name')[:127 - len(tag)]), tag)
def createFileName(self, data, filedata, movie):
name = os.path.join(self.createNzbName(data, movie))
@@ -41,6 +47,18 @@ class Downloader(Plugin):
return is_correct
def magnetToTorrent(self, magnet_link):
torrent_hash = re.findall('urn:btih:([\w]{40})', magnet_link)[0]
url = 'http://torrage.com/torrent/%s.torrent' % torrent_hash
try:
filedata = self.urlopen(url)
return filedata
except:
log.error('Failed converting magnet url to torrent: %s, %s', (url, traceback.format_exc()))
return False
def isDisabled(self, manual):
return not self.isEnabled(manual)

View File

@@ -8,7 +8,7 @@ log = CPLog(__name__)
class Blackhole(Downloader):
type = ['nzb', 'torrent']
type = ['nzb', 'torrent', 'torrent_magnet']
def download(self, data = {}, movie = {}, manual = False, filedata = None):
if self.isDisabled(manual) or (not self.isCorrectType(data.get('type')) or (not self.conf('use_for') in ['both', data.get('type')])):
@@ -20,8 +20,16 @@ class Blackhole(Downloader):
else:
try:
if not filedata or len(filedata) < 50:
log.error('No nzb/torrent available!')
return False
try:
if data.get('type') == 'torrent_magnet':
filedata = self.magnetToTorrent(data.get('url'))
data['type'] = 'torrent'
except:
log.error('Failed download torrent via magnet url: %s', traceback.format_exc())
if not filedata or len(filedata) < 50:
log.error('No nzb/torrent available: %s', data.get('url'))
return False
fullPath = os.path.join(directory, self.createFileName(data, filedata, movie))

View File

@@ -35,11 +35,17 @@ config = [{
},
{
'name': 'manual',
'default': 0,
'default': False,
'type': 'bool',
'advanced': True,
'description': 'Disable this downloader for automated searches, but use it when I manually send a release.',
},
{
'name': 'delete_failed',
'default': True,
'type': 'bool',
'description': 'Delete a release after the download has failed.',
},
],
}
],

View File

@@ -3,6 +3,7 @@ from couchpotato.core.helpers.encoding import tryUrlencode
from couchpotato.core.helpers.variable import cleanHost
from couchpotato.core.logger import CPLog
import traceback
import json
log = CPLog(__name__)
@@ -39,14 +40,14 @@ class Sabnzbd(Downloader):
try:
if params.get('mode') is 'addfile':
data = self.urlopen(url, timeout = 60, params = {"nzbfile": (nzb_filename, filedata)}, multipart = True, show_error = False)
sab = self.urlopen(url, timeout = 60, params = {"nzbfile": (nzb_filename, filedata)}, multipart = True, show_error = False)
else:
data = self.urlopen(url, timeout = 60, show_error = False)
sab = self.urlopen(url, timeout = 60, show_error = False)
except:
log.error(traceback.format_exc())
log.error('Failed sending release: %s', traceback.format_exc())
return False
result = data.strip()
result = sab.strip()
if not result:
log.error("SABnzbd didn't return anything.")
return False
@@ -61,3 +62,101 @@ class Sabnzbd(Downloader):
else:
log.error("Unknown error: " + result[:40])
return False
def getDownloadStatus(self, data = {}, movie = {}):
if self.isDisabled(manual = True) or not self.isCorrectType(data.get('type')):
return
nzbname = self.createNzbName(data, movie)
log.info('Checking download status of "%s" at SABnzbd.', nzbname)
# Go through Queue
params = {
'apikey': self.conf('api_key'),
'mode': 'queue',
'output': 'json'
}
url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params)
try:
sab = self.urlopen(url, timeout = 60, show_error = False)
except:
log.error('Failed checking status: %s', traceback.format_exc())
return False
try:
history = json.loads(sab)
except:
log.debug("Result text from SAB: " + sab[:40])
log.error('Failed parsing json status: %s', traceback.format_exc())
return False
for slot in history['queue']['slots']:
if slot['cat'] == self.conf('category'):
log.debug('Found %s in SabNZBd queue, which is %s, with %s left', (slot['filename'], slot['status'], slot['timeleft']))
if slot['filename'] == nzbname:
return slot['status'].lower()
# Go through history items
params = {
'apikey': self.conf('api_key'),
'mode': 'history',
'output': 'json'
}
url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params)
try:
sab = self.urlopen(url, timeout = 60, show_error = False)
except:
log.error('Failed getting history: %s', traceback.format_exc())
return
try:
history = json.loads(sab)
except:
log.debug("Result text from SAB: " + sab[:40])
log.error('Failed parsing history json: %s', traceback.format_exc())
return
for slot in history['history']['slots']:
if slot['category'] == self.conf('category'):
log.debug('Found %s in SabNZBd history, which has %s', (slot['name'], slot['status']))
if slot['name'] == nzbname:
if slot['status'] == 'Failed' or slot['fail_message'].strip():
# Delete failed download
if self.conf('delete_failed', default = True):
log.info('%s failed downloading, deleting...', slot['name'])
params = {
'apikey': self.conf('api_key'),
'mode': 'history',
'name': 'delete',
'del_files': '1',
'value': slot['nzo_id']
}
url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params)
try:
sab = self.urlopen(url, timeout = 60, show_error = False)
except:
log.error('Failed deleting: %s', traceback.format_exc())
return False
result = sab.strip()
if not result:
log.error("SABnzbd didn't return anything.")
log.debug("Result text from SAB: " + result[:40])
if result == "ok":
log.info('SabNZBd deleted failed release %s successfully.', slot['name'])
elif result == "Missing authentication":
log.error("Incorrect username/password or API?.")
else:
log.error("Unknown error: " + result[:40])
return 'failed'
else:
return slot['status'].lower()
return 'not_found'

View File

@@ -216,7 +216,7 @@ var NotificationBase = new Class({
},
testButtonName: function(fieldset){
var name = fieldset.getElement('h2').get('text');
var name = String(fieldset.getElement('h2').innerHTML).substring(0,String(fieldset.getElement('h2').innerHTML).indexOf("<span")); //.get('text');
return 'Test '+name;
}

View File

@@ -19,7 +19,7 @@ class NMJ(Notification):
def __init__(self):
addEvent('renamer.after', self.addToLibrary)
addApiView(self.testNotifyName(), self.test)
addApiView('notify.nmj.auto_config', self.autoConfig)
def autoConfig(self):
@@ -76,7 +76,7 @@ class NMJ(Notification):
mount = self.conf('mount')
database = self.conf('database')
if self.mount:
if mount:
log.debug('Try to mount network drive via url: %s', (mount))
try:
data = self.urlopen(mount)
@@ -114,3 +114,8 @@ class NMJ(Notification):
def failed(self):
return jsonified({'success': False})
def test(self):
return jsonified({'success': self.addToLibrary()})

View File

@@ -15,14 +15,22 @@ class NotifyMyAndroid(Notification):
nma.addkey(keys)
nma.developerkey(self.conf('dev_key'))
# hacky fix for the event type
# hacky fix for the event type
# as it seems to be part of the message now
self.event = message.split(' ')[0]
response = nma.push(
application = self.default_title,
event = self.event,
description = message,
priority = self.conf('priority'),
batch_mode = len(keys) > 1
)
response = nma.push(self.default_title, self.event , message, self.conf('priority'), batch_mode = len(keys) > 1)
successful = 0
for key in keys:
if not response[str(key)]['code'] == u'200':
log.error('Could not send notification to NotifyMyAndroid (%s). %s', (key, response[key]['message']))
else:
successful += 1
return response
return successful == len(keys)

View File

@@ -13,11 +13,15 @@ class XBMC(Notification):
def notify(self, message = '', data = {}, listener = None):
if self.isDisabled(): return
for host in [x.strip() for x in self.conf('host').split(",")]:
self.send({'command': 'ExecBuiltIn', 'parameter': 'Notification(CouchPotato, %s)' % message}, host)
self.send({'command': 'ExecBuiltIn', 'parameter': 'XBMC.updatelibrary(video)'}, host)
hosts = [x.strip() for x in self.conf('host').split(",")]
successful = 0
for host in hosts:
if self.send({'command': 'ExecBuiltIn', 'parameter': 'Notification(CouchPotato, %s)' % message}, host):
successful += 1
if self.send({'command': 'ExecBuiltIn', 'parameter': 'XBMC.updatelibrary(video)'}, host):
successful += 1
return True
return successful == len(hosts)*2
def send(self, command, host):

View File

@@ -340,6 +340,32 @@
.movies .movie .hide_trailer.hide {
top: -30px;
}
.movies .movie .try_container {
padding: 5px 10px;
text-align: center;
}
.movies .movie .try_container a {
margin: 0 5px;
padding: 2px 5px;
}
.movies .movie .releases .next_release {
border-left: 6px solid #2aa300;
}
.movies .movie .releases .next_release > :first-child {
margin-left: -6px;
}
.movies .movie .releases .last_release {
border-left: 6px solid #ffa200;
}
.movies .movie .releases .last_release > :first-child {
margin-left: -6px;
}
.movies .load_more {
display: block;

View File

@@ -140,28 +140,33 @@ var Movie = new Class({
self.profile.getTypes().each(function(type){
var q = self.addQuality(type.quality_id || type.get('quality_id'));
if(type.finish == true || type.get('finish'))
if((type.finish == true || type.get('finish')) && !q.hasClass('finish')){
q.addClass('finish');
q.set('title', q.get('title') + ' Will finish searching for this movie if this quality is found.')
}
});
// Add done releases
Array.each(self.data.releases, function(release){
self.data.releases.each(function(release){
var q = self.quality.getElement('.q_id'+ release.quality_id),
status = Status.get(release.status_id);
if(!q && (status.identifier == 'snatched' || status.identifier == 'done'))
var q = self.addQuality(release.quality_id)
if (status && q)
if (status && q && !q.hasClass(status.identifier)){
q.addClass(status.identifier);
q.set('title', (q.get('title') ? q.get('title') : '') + ' status: '+ status.label)
}
});
Object.each(self.options.actions, function(action, key){
self.actions.adopt(
self.action[key.toLowerCase()] = new self.options.actions[key](self)
)
self.action[key.toLowerCase()] = action = new self.options.actions[key](self)
if(action.el)
self.actions.adopt(action)
});
if(!self.data.library.rating)
@@ -175,7 +180,8 @@ var Movie = new Class({
var q = Quality.getQuality(quality_id);
return new Element('span', {
'text': q.label,
'class': 'q_'+q.identifier + ' q_id' + q.id
'class': 'q_'+q.identifier + ' q_id' + q.id,
'title': ''
}).inject(self.quality);
},
@@ -280,6 +286,31 @@ var MovieAction = new Class({
this.el.removeClass('disable')
},
createMask: function(){
var self = this;
self.mask = new Element('div.mask', {
'styles': {
'z-index': '1'
}
}).inject(self.movie, 'top').fade('hide');
self.positionMask();
},
positionMask: function(){
var self = this,
movie = $(self.movie),
s = movie.getSize()
return;
return self.mask.setStyles({
'width': s.x,
'height': s.y
}).position({
'relativeTo': movie
})
},
toElement: function(){
return this.el || null
}
@@ -298,19 +329,10 @@ var IMDBAction = new Class({
self.el = new Element('a.imdb', {
'title': 'Go to the IMDB page of ' + self.movie.getTitle(),
'events': {
'click': self.gotoIMDB.bind(self)
}
'href': 'http://www.imdb.com/title/'+self.id+'/'
});
if(!self.id) self.disable();
},
gotoIMDB: function(e){
var self = this;
(e).preventDefault();
window.open('http://www.imdb.com/title/'+self.id+'/');
}
});
@@ -318,13 +340,10 @@ var IMDBAction = new Class({
var ReleaseAction = new Class({
Extends: MovieAction,
id: null,
create: function(){
var self = this;
self.id = self.movie.get('identifier');
self.el = new Element('a.releases.icon.download', {
'title': 'Show the releases that are available for ' + self.movie.getTitle(),
'events': {
@@ -332,15 +351,33 @@ var ReleaseAction = new Class({
}
});
var buttons_done = false;
self.movie.data.releases.sortBy('-info.score').each(function(release){
if(buttons_done) return;
var status = Status.get(release.status_id);
if((status.identifier == 'ignored' || status.identifier == 'failed') || (!self.next_release && status.identifier == 'available')){
self.hide_on_click = false;
self.show();
buttons_done = true;
}
});
},
show: function(e){
var self = this;
(e).preventDefault();
if(e)
(e).preventDefault();
if(!self.options_container){
self.options_container = new Element('div.options').adopt(
self.release_container = new Element('div.releases.table')
self.release_container = new Element('div.releases.table').adopt(
self.trynext_container = new Element('div.buttons.try_container')
)
).inject(self.movie, 'top');
// Header
@@ -354,29 +391,35 @@ var ReleaseAction = new Class({
new Element('span.provider', {'text': 'Provider'})
).inject(self.release_container)
Array.each(self.movie.data.releases, function(release){
self.movie.data.releases.sortBy('-info.score').each(function(release){
var status = Status.get(release.status_id),
quality = Quality.getProfile(release.quality_id) || {},
info = release.info;
try {
var details_url = info.filter(function(item){ return item.identifier == 'detail_url' }).pick().value;
} catch(e){}
if( status.identifier == 'ignored' || status.identifier == 'failed'){
self.last_release = release;
}
else if(!self.next_release && status.identifier == 'available'){
self.next_release = release;
}
// Create release
new Element('div', {
'class': 'item '+status.identifier,
'class': 'item '+status.identifier +
(self.next_release && self.next_release.id == release.id ? ' next_release' : '') +
(self.last_release && self.last_release.id == release.id ? ' last_release' : ''),
'id': 'release_'+release.id
}).adopt(
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.quality', {'text': quality.label || 'n/a'}),
new Element('span.size', {'text': (self.get(release, 'size'))}),
new Element('span.quality', {'text': quality.get('label') || 'n/a'}),
new Element('span.size', {'text': release.info['size'] ? Math.floor(self.get(release, 'size')) : 'n/a'}),
new Element('span.age', {'text': self.get(release, 'age')}),
new Element('span.score', {'text': self.get(release, 'score')}),
new Element('span.provider', {'text': self.get(release, 'provider')}),
details_url ? new Element('a.info.icon', {
'href': details_url,
release.info['detail_url'] ? new Element('a.info.icon', {
'href': release.info['detail_url'],
'target': '_blank'
}) : null,
new Element('a.download.icon', {
@@ -400,17 +443,37 @@ var ReleaseAction = new Class({
).inject(self.release_container)
});
self.trynext_container.adopt(
new Element('span.or', {
'text': 'Download'
}),
self.last_release ? new Element('a.button.orange', {
'text': 'the same release again',
'events': {
'click': self.trySameRelease.bind(self)
}
}) : null,
self.next_release && self.last_release ? new Element('span.or', {
'text': 'or'
}) : null,
self.next_release ? [new Element('a.button.green', {
'text': self.last_release ? 'another release' : 'the best release',
'events': {
'click': self.tryNextRelease.bind(self)
}
}),
new Element('span.or', {
'text': 'or pick one below'
})] : null
)
}
self.movie.slide('in', self.options_container);
},
get: function(release, type){
var self = this;
return (release.info.filter(function(info){
return type == info.identifier
}).pick() || {}).value || 'n/a'
return release.info[type] || 'n/a'
},
download: function(release){
@@ -444,6 +507,25 @@ var ReleaseAction = new Class({
}
})
},
tryNextRelease: function(movie_id){
var self = this;
if(self.last_release)
self.ignore(self.last_release);
if(self.next_release)
self.download(self.next_release);
},
trySameRelease: function(movie_id){
var self = this;
if(self.last_release)
self.download(self.last_release);
}
});

View File

@@ -186,7 +186,6 @@
.movie_result .info h2 span {
padding: 0 5px;
content: ")";
}
.movie_result .info h2 span:before { content: "("; }

View File

@@ -170,7 +170,9 @@ class Release(Plugin):
# Get matching provider
provider = fireEvent('provider.belongs_to', item['url'], provider = item.get('provider'), single = True)
item['download'] = provider.download
if item['type'] != 'torrent_magnet':
item['download'] = provider.download
success = fireEvent('searcher.download', data = item, movie = rel.movie.to_dict({
'profile': {'types': {'quality': {}}},

View File

@@ -86,15 +86,6 @@ config = [{
'label': 'Separator',
'description': 'Replace all the spaces with a character. Example: ".", "-" (without quotes). Leave empty to use spaces.',
},
{
'advanced': True,
'name': 'run_every',
'label': 'Run every',
'default': 1,
'type': 'int',
'unit': 'min(s)',
'description': 'Search for new movies inside the folder every X minutes.',
},
],
}, {
'tab': 'renamer',

View File

@@ -30,7 +30,7 @@ class Renamer(Plugin):
addEvent('renamer.scan', self.scan)
addEvent('app.load', self.scan)
fireEvent('schedule.interval', 'renamer.scan', self.scan, minutes = self.conf('run_every'))
#fireEvent('schedule.interval', 'renamer.scan', self.scan, minutes = self.conf('run_every'))
def scanView(self):
@@ -327,6 +327,11 @@ class Renamer(Plugin):
try:
if os.path.isfile(src):
os.remove(src)
parent_dir = os.path.normpath(os.path.dirname(src))
if os.path.isdir(parent_dir) and destination != parent_dir:
self.deleteEmptyFolder(parent_dir, show_error = False)
except:
log.error('Failed removing %s: %s', (src, traceback.format_exc()))
self.tagDir(group, 'failed_remove')
@@ -464,8 +469,9 @@ class Renamer(Plugin):
def replaceDoubles(self, string):
return string.replace(' ', ' ').replace(' .', '.')
def deleteEmptyFolder(self, folder):
def deleteEmptyFolder(self, folder, show_error = True):
loge = log.error if show_error else log.debug
for root, dirs, files in os.walk(folder):
for dir_name in dirs:
@@ -474,9 +480,9 @@ class Renamer(Plugin):
try:
os.rmdir(full_path)
except:
log.error('Couldn\'t remove empty directory %s: %s', (full_path, traceback.format_exc()))
loge('Couldn\'t remove empty directory %s: %s', (full_path, traceback.format_exc()))
try:
os.rmdir(folder)
except:
log.error('Couldn\'t remove empty directory %s: %s', (folder, traceback.format_exc()))
loge('Couldn\'t remove empty directory %s: %s', (folder, traceback.format_exc()))

View File

@@ -303,7 +303,15 @@ class Scanner(Plugin):
break
if file_too_new:
log.info('Files seem to be still unpacking or just unpacked (created on %s), ignoring for now: %s', (time.ctime(file_time[0]), identifier))
try:
time_string = time.ctime(file_time[0])
except:
try:
time_string = time.ctime(file_time[1])
except:
time_string = 'unknown'
log.info('Files seem to be still unpacking or just unpacked (created on %s), ignoring for now: %s', (time_string, identifier))
# Delete the unsorted list
del group['unsorted_files']

View File

@@ -39,6 +39,21 @@ config = [{
'type': 'dropdown',
'values': [('usenet & torrents', 'both'), ('usenet', 'nzb'), ('torrents', 'torrent')],
},
{
'advanced': True,
'name': 'run_every',
'label': 'Run every',
'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',
},
{
'name': 'next_on_failed',
'default': True,
'type': 'bool',
'description': 'Try the next best release for a movie after a download failed.',
},
],
}, {
'tab': 'searcher',

View File

@@ -1,6 +1,8 @@
from couchpotato import get_session
from couchpotato.api import addApiView
from couchpotato.core.event import addEvent, fireEvent
from couchpotato.core.helpers.encoding import simplifyString, toUnicode
from couchpotato.core.helpers.request import jsonified, getParam
from couchpotato.core.helpers.variable import md5, getImdb, getTitle
from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin
@@ -25,9 +27,19 @@ class Searcher(Plugin):
addEvent('searcher.single', self.single)
addEvent('searcher.correct_movie', self.correctMovie)
addEvent('searcher.download', self.download)
addEvent('searcher.check_snatched', self.checkSnatched)
addApiView('searcher.try_next', self.tryNextReleaseView, docs = {
'desc': 'Marks the snatched results as ignored and try the next best release',
'params': {
'id': {'desc': 'The id of the movie'},
},
})
# Schedule cronjob
fireEvent('schedule.cron', 'searcher.all', self.all_movies, day = self.conf('cron_day'), hour = self.conf('cron_hour'), minute = self.conf('cron_minute'))
fireEvent('schedule.interval', 'searcher.check_snatched', self.checkSnatched, minutes = self.conf('run_every'))
def all_movies(self):
@@ -138,7 +150,7 @@ class Searcher(Plugin):
for info in nzb:
try:
if not isinstance(nzb[info], (str, unicode, int, long)):
if not isinstance(nzb[info], (str, unicode, int, long, float)):
continue
rls_info = ReleaseInfo(
@@ -239,7 +251,6 @@ class Searcher(Plugin):
def correctMovie(self, nzb = {}, movie = {}, quality = {}, **kwargs):
imdb_results = kwargs.get('imdb_results', False)
single_category = kwargs.get('single_category', False)
retention = Env.setting('retention', section = 'nzb')
if nzb.get('seeds') is None and retention < nzb.get('age', 0):
@@ -272,7 +283,7 @@ class Searcher(Plugin):
preferred_quality = fireEvent('quality.single', identifier = quality['identifier'], single = True)
# Contains lower quality string
if self.containsOtherQuality(nzb, movie_year = movie['library']['year'], preferred_quality = preferred_quality, single_category = single_category):
if self.containsOtherQuality(nzb, movie_year = movie['library']['year'], preferred_quality = preferred_quality):
log.info('Wrong: %s, looking for %s', (nzb['name'], quality['label']))
return False
@@ -324,7 +335,7 @@ class Searcher(Plugin):
log.info("Wrong: %s, undetermined naming. Looking for '%s (%s)'" % (nzb['name'], movie_name, movie['library']['year']))
return False
def containsOtherQuality(self, nzb, movie_year = None, preferred_quality = {}, single_category = False):
def containsOtherQuality(self, nzb, movie_year = None, preferred_quality = {}):
name = nzb['name']
size = nzb.get('size', 0)
@@ -355,9 +366,6 @@ class Searcher(Plugin):
if found.get(allowed):
del found[allowed]
if (len(found) == 0 and single_category):
return False
return not (found.get(preferred_quality['identifier']) and len(found) == 1)
def checkIMDB(self, haystack, imdbId):
@@ -439,3 +447,96 @@ class Searcher(Plugin):
return False
def checkSnatched(self):
snatched_status = fireEvent('status.get', 'snatched', single = True)
ignored_status = fireEvent('status.get', 'ignored', single = True)
failed_status = fireEvent('status.get', 'failed', single = True)
db = get_session()
rels = db.query(Release).filter_by(status_id = snatched_status.get('id'))
if rels:
log.debug('Checking status snatched releases...')
scanrequired = False
for rel in rels:
# Get current selected title
default_title = ''
for title in rel.movie.library.titles:
if title.default: default_title = title.title
log.debug('Checking snatched movie: %s' , default_title)
item = {}
for info in rel.info:
item[info.identifier] = info.value
movie_dict = fireEvent('movie.get', rel.movie_id, single = True)
# check status
downloadstatus = fireEvent('download.status', data = item, movie = movie_dict, single = True)
if not downloadstatus:
log.debug('Download status functionality is not implemented for active downloaders.')
scanrequired = True
else:
log.debug('Download status: %s' , downloadstatus)
if downloadstatus == 'failed':
if self.conf('next_on_failed'):
self.tryNextRelease(rel.movie_id)
else:
rel.status_id = failed_status.get('id')
db.commit()
log.info('Download of %s failed.', item['name'])
elif downloadstatus == 'completed':
log.info('Download of %s completed!', item['name'])
scanrequired = True
elif downloadstatus == 'not_found':
log.info('%s not found in downloaders', item['name'])
rel.status_id = ignored_status.get('id')
db.commit()
# Note that Queued, Downloading, Paused, Repair and Unpackimg are also available as status for SabNZBd
if scanrequired:
fireEvent('renamer.scan')
def tryNextReleaseView(self):
trynext = self.tryNextRelease(getParam('id'))
return jsonified({
'success': trynext
})
def tryNextRelease(self, movie_id, manual = False):
snatched_status = fireEvent('status.get', 'snatched', single = True)
ignored_status = fireEvent('status.get', 'ignored', single = True)
try:
movie_dict = fireEvent('movie.get', movie_id, single = True)
db = get_session()
rels = db.query(Release).filter_by(
status_id = snatched_status.get('id'),
movie_id = movie_id
).all()
for rel in rels:
rel.status_id = ignored_status.get('id')
db.commit()
log.info('Trying next release for: %s', getTitle(movie_dict['library']))
fireEvent('searcher.single', movie_dict)
return True
except:
log.error('Failed searching for next release: %s', traceback.format_exc())
return False

View File

@@ -19,6 +19,7 @@ class StatusPlugin(Plugin):
'downloaded': 'Downloaded',
'wanted': 'Wanted',
'snatched': 'Snatched',
'failed': 'Failed',
'deleted': 'Deleted',
'ignored': 'Ignored',
}

View File

@@ -10,7 +10,7 @@ config = [{
'tab': 'searcher',
'subtab': 'providers',
'name': 'Mysterbin',
'description': '',
'description': 'Free provider, less accurate. See <a href="http://www.mysterbin.com/">Mysterbin</a>',
'options': [
{
'name': 'enabled',

View File

@@ -88,7 +88,7 @@ class Mysterbin(NZBProvider):
new['score'] = fireEvent('score.calculate', new, movie, single = True)
is_correct_movie = fireEvent('searcher.correct_movie',
nzb = new, movie = movie, quality = quality,
imdb_results = False, single_category = False, single = True)
imdb_results = False, single = True)
if is_correct_movie:
results.append(new)
self.found(new)

View File

@@ -10,6 +10,7 @@ config = [{
'tab': 'searcher',
'subtab': 'providers',
'name': 'newzbin',
'description': 'See <a href="https://www.newzbin2.es/">Newzbin</a>',
'wizard': True,
'options': [
{

View File

@@ -96,13 +96,12 @@ class Newznab(NZBProvider, RSS):
url = "%s&%s" % (self.getUrl(host['host'], self.urls['search']), arguments)
cache_key = 'newznab.%s.%s.%s' % (host['host'], movie['library']['identifier'], cat_id[0])
single_cat = (len(cat_id) == 1 and cat_id[0] != self.cat_backup_id)
results = self.createItems(url, cache_key, host, single_cat = single_cat, movie = movie, quality = quality)
results = self.createItems(url, cache_key, host, movie = movie, quality = quality)
return results
def createItems(self, url, cache_key, host, single_cat = False, movie = None, quality = None, for_feed = False):
def createItems(self, url, cache_key, host, movie = None, quality = None, for_feed = False):
results = []
data = self.getCache(cache_key, url, cache_timeout = 1800, headers = {'User-Agent': Env.getIdentifier()})
@@ -146,7 +145,7 @@ class Newznab(NZBProvider, RSS):
if not for_feed:
is_correct_movie = fireEvent('searcher.correct_movie',
nzb = new, movie = movie, quality = quality,
imdb_results = True, single_category = single_cat, single = True)
imdb_results = True, single = True)
if is_correct_movie:
new['score'] = fireEvent('score.calculate', new, movie, single = True)

View File

@@ -10,7 +10,7 @@ config = [{
'tab': 'searcher',
'subtab': 'providers',
'name': 'NZBClub',
'description': '',
'description': 'Free provider, less accurate. See <a href="https://www.nzbclub.com/">NZBClub</a>',
'options': [
{
'name': 'enabled',

View File

@@ -86,7 +86,7 @@ class NZBClub(NZBProvider, RSS):
is_correct_movie = fireEvent('searcher.correct_movie',
nzb = new, movie = movie, quality = quality,
imdb_results = False, single_category = False, single = True)
imdb_results = False, single = True)
if is_correct_movie:
new['score'] = fireEvent('score.calculate', new, movie, single = True)

View File

@@ -10,7 +10,7 @@ config = [{
'tab': 'searcher',
'subtab': 'providers',
'name': 'nzbindex',
'description': 'Free provider, but less accurate.',
'description': 'Free provider, less accurate. See <a href="http://www.nzbindex.nl/">NZBIndex</a>',
'options': [
{
'name': 'enabled',

View File

@@ -93,7 +93,7 @@ class NzbIndex(NZBProvider, RSS):
is_correct_movie = fireEvent('searcher.correct_movie',
nzb = new, movie = movie, quality = quality,
imdb_results = False, single_category = False, single = True)
imdb_results = False, single = True)
if is_correct_movie:
new['score'] = fireEvent('score.calculate', new, movie, single = True)

View File

@@ -11,6 +11,7 @@ config = [{
'subtab': 'providers',
'name': 'nzbmatrix',
'label': 'NZBMatrix',
'description': 'See <a href="https://nzbmatrix.com/">NZBMatrix</a>',
'wizard': True,
'options': [
{

View File

@@ -22,7 +22,7 @@ class NZBMatrix(NZBProvider, RSS):
cat_ids = [
([50], ['bd50']),
([42, 53], ['720p', '1080p']),
([2], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr']),
([2, 9], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr']),
([54], ['brrip']),
([1], ['dvdr']),
]
@@ -49,7 +49,6 @@ class NZBMatrix(NZBProvider, RSS):
url = "%s?%s" % (self.urls['search'], arguments)
cache_key = 'nzbmatrix.%s.%s' % (movie['library'].get('identifier'), cat_ids)
single_cat = True
data = self.getCache(cache_key, url, cache_timeout = 1800, headers = {'User-Agent': Env.getIdentifier()})
if data:
@@ -86,7 +85,7 @@ class NZBMatrix(NZBProvider, RSS):
is_correct_movie = fireEvent('searcher.correct_movie',
nzb = new, movie = movie, quality = quality,
imdb_results = True, single_category = single_cat, single = True)
imdb_results = True, single = True)
if is_correct_movie:
new['score'] = fireEvent('score.calculate', new, movie, single = True)

View File

@@ -0,0 +1,40 @@
from .main import Nzbsrus
def start():
return Nzbsrus()
config = [{
'name': 'nzbsrus',
'groups': [
{
'tab': 'searcher',
'subtab': 'providers',
'name': 'nzbsrus',
'label': 'Nzbsrus',
'description': 'See <a href="https://www.nzbsrus.com/">NZBsRus</a>',
'wizard': True,
'options': [
{
'name': 'enabled',
'type': 'enabler',
},
{
'name': 'userid',
'label': 'User ID',
},
{
'name': 'api_key',
'default': '',
'label': 'Api Key',
},
{
'name': 'english_only',
'default': 1,
'type': 'bool',
'label': 'English only',
'description': 'Only search for English spoken movies on Nzbsrus',
},
],
},
],
}]

View File

@@ -0,0 +1,104 @@
from couchpotato.core.event import fireEvent
from couchpotato.core.helpers.encoding import tryUrlencode
from couchpotato.core.helpers.rss import RSS
from couchpotato.core.logger import CPLog
from couchpotato.core.providers.nzb.base import NZBProvider
from couchpotato.environment import Env
import time
import xml.etree.ElementTree as XMLTree
log = CPLog(__name__)
class Nzbsrus(NZBProvider, RSS):
urls = {
'download': 'https://www.nzbsrus.com/nzbdownload_rss.php/%s',
'detail': 'https://www.nzbsrus.com/nzbdetails.php?id=%s',
'search': 'https://www.nzbsrus.com/api.php?extended=1&xml=1&listname={date,grabs}',
}
cat_ids = [
([90, 45, 51], ['720p', '1080p', 'brrip', 'bd50', 'dvdr']),
([48, 51], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr']),
]
cat_backup_id = 240
def search(self, movie, quality):
results = []
if self.isDisabled():
return results
cat_id_string = '&'.join(['c%s=1' % x for x in self.getCatId(quality.get('identifier'))])
arguments = tryUrlencode({
'searchtext': 'imdb:' + movie['library']['identifier'][2:],
'uid': self.conf('userid'),
'key': self.conf('api_key'),
'age': Env.setting('retention', section = 'nzb'),
})
# check for english_only
if self.conf('english_only'):
arguments += "&lang0=1&lang3=1&lang1=1"
url = "%s&%s&%s" % (self.urls['search'], arguments , cat_id_string)
cache_key = 'nzbsrus_1.%s.%s' % (movie['library'].get('identifier'), cat_id_string)
single_cat = True
data = self.getCache(cache_key, url, cache_timeout = 1800, headers = {'User-Agent': Env.getIdentifier()})
if data:
try:
try:
data = XMLTree.fromstring(data)
nzbs = self.getElements(data, 'results/result')
except Exception, e:
log.debug('%s, %s', (self.getName(), e))
return results
for nzb in nzbs:
title = self.getTextElement(nzb, "name")
if 'error' in title.lower(): continue
id = self.getTextElement(nzb, "id")
size = int(round(int(self.getTextElement(nzb, "size")) / 1048576))
age = int(round((time.time() - int(self.getTextElement(nzb, "postdate"))) / 86400))
new = {
'id': id,
'type': 'nzb',
'provider': self.getName(),
'name': title,
'age': age,
'size': size,
'url': self.urls['download'] % id + self.getApiExt() + self.getTextElement(nzb, "key"),
'download': self.download,
'detail_url': self.urls['detail'] % id,
'description': self.getTextElement(nzb, "addtext"),
'check_nzb': True,
}
is_correct_movie = fireEvent('searcher.correct_movie',
nzb = new, movie = movie, quality = quality,
imdb_results = True, single = True)
if is_correct_movie:
new['score'] = fireEvent('score.calculate', new, movie, single = True)
results.append(new)
self.found(new)
return results
except SyntaxError:
log.error('Failed to parse XML response from Nzbsrus.com')
return results
def download(self, url = '', nzb_id = ''):
return self.urlopen(url, headers = {'User-Agent': Env.getIdentifier()})
def getApiExt(self):
return '/%s/' % (self.conf('userid'))

View File

@@ -10,6 +10,7 @@ config = [{
'tab': 'searcher',
'subtab': 'providers',
'name': 'KickAssTorrents',
'description': 'See <a href="https://kat.ph/">KickAssTorrents</a>',
'options': [
{
'name': 'enabled',

View File

@@ -3,8 +3,6 @@ from couchpotato.core.event import fireEvent
from couchpotato.core.helpers.variable import tryInt
from couchpotato.core.logger import CPLog
from couchpotato.core.providers.torrent.base import TorrentProvider
import StringIO
import gzip
import re
import traceback
@@ -14,10 +12,9 @@ log = CPLog(__name__)
class KickAssTorrents(TorrentProvider):
urls = {
'test': 'http://www.kat.ph/',
'detail': 'http://www.kat.ph/%s-t%s.html',
'search': 'http://www.kat.ph/i%s/',
'download': 'http://torcache.net/',
'test': 'http://kat.ph/',
'detail': 'http://kat.ph/%s',
'search': 'http://kat.ph/i%s/',
}
cat_ids = [
@@ -60,11 +57,10 @@ class KickAssTorrents(TorrentProvider):
continue
new = {
'type': 'torrent',
'type': 'torrent_magnet',
'check_nzb': False,
'description': '',
'provider': self.getName(),
'download': self.download,
'score': 0,
}
@@ -77,9 +73,8 @@ class KickAssTorrents(TorrentProvider):
link = td.find('div', {'class': 'torrentname'}).find_all('a')[1]
new['id'] = temp.get('id')[-8:]
new['name'] = link.text
new['url'] = td.find_all('a', 'idownload')[1]['href']
if new['url'][:2] == '//':
new['url'] = 'http:%s' % new['url']
new['url'] = td.find('a', 'imagnet')['href']
new['detail_url'] = self.urls['detail'] % link['href'][1:]
new['score'] = 20 if td.find('a', 'iverif') else 0
elif column_name is 'size':
new['size'] = self.parseSize(td.text)
@@ -95,7 +90,7 @@ class KickAssTorrents(TorrentProvider):
new['score'] += fireEvent('score.calculate', new, movie, single = True)
is_correct_movie = fireEvent('searcher.correct_movie',
nzb = new, movie = movie, quality = quality,
imdb_results = True, single_category = False, single = True)
imdb_results = True, single = True)
if is_correct_movie:
results.append(new)
self.found(new)
@@ -129,13 +124,3 @@ class KickAssTorrents(TorrentProvider):
age += tryInt(nr) * mult
return tryInt(age)
def download(self, url = '', nzb_id = ''):
compressed_data = self.urlopen(url = url, headers = {'Referer': 'http://kat.ph/'})
compressedstream = StringIO.StringIO(compressed_data)
gzipper = gzip.GzipFile(fileobj = compressedstream)
data = gzipper.read()
return data

View File

@@ -10,7 +10,7 @@ config = [{
'tab': 'searcher',
'subtab': 'providers',
'name': 'PublicHD',
'description': 'Public Torrent site with only HD content.',
'description': 'Public Torrent site with only HD content. See <a href="http://publichd.eu/">PublicHD</a>',
'options': [
{
'name': 'enabled',
@@ -20,4 +20,4 @@ config = [{
],
},
],
}]
}]

View File

@@ -15,19 +15,9 @@ class PublicHD(TorrentProvider):
urls = {
'test': 'http://publichd.eu',
'download': 'http://publichd.eu/%s',
'detail': 'http://publichd.eu/index.php?page=torrent-details&id=%s',
'search': 'http://publichd.eu/index.php',
}
cat_ids = [
([9], ['bd50']),
([5], ['1080p']),
([2], ['720p']),
([15, 16], ['brrip']),
]
cat_backup_id = 0
http_time_between_calls = 0
def search(self, movie, quality):
@@ -39,9 +29,8 @@ class PublicHD(TorrentProvider):
params = tryUrlencode({
'page':'torrents',
'search': getTitle(movie['library']) + ' ' + quality['identifier'],
'search': '%s %s' % (getTitle(movie['library']), movie['library']['year']),
'active': 1,
'category': self.getCatId(quality['identifier'])[0]
})
url = '%s?%s' % (self.urls['search'], params)
@@ -58,7 +47,7 @@ class PublicHD(TorrentProvider):
for result in entries[2:len(entries) - 1]:
info_url = result.find(href = re.compile('torrent-details'))
download = result.find(href = re.compile('\.torrent'))
download = result.find(href = re.compile('magnet:'))
if info_url and download:
@@ -67,12 +56,11 @@ class PublicHD(TorrentProvider):
new = {
'id': url['id'][0],
'name': info_url.string,
'type': 'torrent',
'type': 'torrent_magnet',
'check_nzb': False,
'description': '',
'provider': self.getName(),
'download': self.download,
'url': self.urls['download'] % download['href'],
'url': download['href'],
'detail_url': self.urls['detail'] % url['id'][0],
'size': self.parseSize(result.find_all('td')[7].string),
'seeders': tryInt(result.find_all('td')[4].string),
@@ -82,7 +70,7 @@ class PublicHD(TorrentProvider):
new['score'] = fireEvent('score.calculate', new, movie, single = True)
is_correct_movie = fireEvent('searcher.correct_movie', nzb = new, movie = movie, quality = quality,
imdb_results = False, single_category = False, single = True)
imdb_results = False, single = True)
if is_correct_movie:
results.append(new)

View File

@@ -10,6 +10,7 @@ config = [{
'tab': 'searcher',
'subtab': 'providers',
'name': 'SceneAccess',
'description': 'See <a href="https://sceneaccess.eu/">SceneAccess</a>',
'options': [
{
'name': 'enabled',

View File

@@ -86,7 +86,7 @@ class SceneAccess(TorrentProvider):
new['score'] = fireEvent('score.calculate', new, movie, single = True)
is_correct_movie = fireEvent('searcher.correct_movie', nzb = new, movie = movie, quality = quality,
imdb_results = False, single_category = False, single = True)
imdb_results = False, single = True)
if is_correct_movie:
results.append(new)

View File

@@ -10,6 +10,7 @@ config = [{
'tab': 'searcher',
'subtab': 'providers',
'name': 'SceneHD',
'description': 'See <a href="http://scenehd.org">SceneHD</a>',
'options': [
{
'name': 'enabled',

View File

@@ -79,7 +79,7 @@ class SceneHD(TorrentProvider):
new['score'] = fireEvent('score.calculate', new, movie, single = True)
is_correct_movie = fireEvent('searcher.correct_movie', nzb = new, movie = movie, quality = quality,
imdb_results = imdb_results, single_category = False, single = True)
imdb_results = imdb_results, single = True)
if is_correct_movie:
results.append(new)

View File

@@ -9,7 +9,7 @@ config = [{
'tab': 'searcher',
'subtab': 'providers',
'name': 'ThePirateBay',
'description': 'The world\'s largest bittorrent tracker.',
'description': 'The world\'s largest bittorrent tracker. See <a href="http://fucktimkuik.org/">ThePirateBay</a>',
'options': [
{
'name': 'enabled',

View File

@@ -120,7 +120,7 @@ class ThePirateBay(TorrentProvider):
new['score'] = fireEvent('score.calculate', new, movie, single = True)
is_correct_movie = fireEvent('searcher.correct_movie', nzb = new, movie = movie, quality = quality,
imdb_results = False, single_category = False, single = True)
imdb_results = False, single = True)
if is_correct_movie:
results.append(new)

View File

@@ -10,6 +10,7 @@ config = [{
'tab': 'searcher',
'subtab': 'providers',
'name': 'TorrentLeech',
'description': 'See <a href="http://torrentleech.org">TorrentLeech</a>',
'options': [
{
'name': 'enabled',

View File

@@ -80,7 +80,7 @@ class TorrentLeech(TorrentProvider):
new['score'] = fireEvent('score.calculate', new, movie, single = True)
is_correct_movie = fireEvent('searcher.correct_movie', nzb = new, movie = movie, quality = quality,
imdb_results = imdb_results, single_category = False, single = True)
imdb_results = imdb_results, single = True)
if is_correct_movie:
results.append(new)

View File

@@ -103,6 +103,22 @@ class Release(Entity):
files = ManyToMany('File', cascade = 'all, delete-orphan', single_parent = True)
info = OneToMany('ReleaseInfo', cascade = 'all, delete-orphan')
def to_dict(self, deep = {}, exclude = []):
orig_dict = super(Release, self).to_dict(deep = deep, exclude = exclude)
new_info = {}
for info in orig_dict.get('info', []):
value = info['value']
try: value = int(info['value'])
except: pass
new_info[info['identifier']] = value
orig_dict['info'] = new_info
return orig_dict
class ReleaseInfo(Entity):
"""Properties that can be bound to a file for off-line usage"""

View File

@@ -29,6 +29,7 @@ var CouchPotato = new Class({
History.addEvent('change', self.openPage.bind(self));
self.c.addEvent('click:relay(a[href^=/]:not([target]))', self.pushState.bind(self));
self.c.addEvent('click:relay(a[href^=http])', self.openDerefered.bind(self));
},
getOption: function(name){
@@ -187,7 +188,7 @@ var CouchPotato = new Class({
restart: function(message, title){
var self = this;
self.blockPage(message || 'Restarting... please wait. If this takes to long, something must have gone wrong.', title);
self.blockPage(message || 'Restarting... please wait. If this takes too long, something must have gone wrong.', title);
Api.request('app.restart');
self.checkAvailable(1000);
},
@@ -216,7 +217,7 @@ var CouchPotato = new Class({
Updater.check(onComplete)
self.blockPage('Please wait. If this takes to long, something must have gone wrong.', 'Checking for updates');
self.blockPage('Please wait. If this takes too long, something must have gone wrong.', 'Checking for updates');
self.checkAvailable(3000);
},
@@ -269,6 +270,17 @@ var CouchPotato = new Class({
createUrl: function(action, params){
return this.options.base_url + (action ? action+'/' : '') + (params ? '?'+Object.toQueryString(params) : '')
},
openDerefered: function(e, el){
(e).stop();
var url = 'http://www.dereferer.org/?' + el.get('href');
if(el.get('target') == '_blank' || (e.meta && Browser.Platform.mac) || (e.control && !Browser.Platform.mac))
window.open(url);
else
window.location = url;
}
});
@@ -419,15 +431,18 @@ function randomString(length, extra) {
return 0;
};
Array.implement('sortBy', function(){
keyPaths.empty();
Array.each(arguments, function(argument) {
switch (typeOf(argument)) {
case "array": saveKeyPath(argument); break;
case "string": saveKeyPath(argument.match(/[+-]|[^.]+/g)); break;
}
});
return this.sort(comparer);
Array.implement({
sortBy: function(){
keyPaths.empty();
Array.each(arguments, function(argument) {
switch (typeOf(argument)) {
case "array": saveKeyPath(argument); break;
case "string": saveKeyPath(argument.match(/[+-]|[^.]+/g)); break;
}
});
return this.sort(comparer);
}
});
})();

View File

@@ -58,6 +58,8 @@ var AboutSettingTab = new Class({
}
}
}),
new Element('dt[text=Updater]'),
self.updater_type = new Element('dd.updater'),
new Element('dt[text=ID]'),
new Element('dd', {'text': App.getOption('pid')}),
new Element('dt[text=Directories]'),
@@ -103,12 +105,8 @@ var AboutSettingTab = new Class({
),
new Element('div.donate', {
'html':
'Or, buy me a (24 pack) Pepsi, for while I\'m coding ;)' +
'<form action="https://www.paypal.com/cgi-bin/webscr" method="post">' +
'<input type="hidden" name="cmd" value="_s-xclick">' +
'<input type="hidden" name="encrypted" value="-----BEGIN PKCS7-----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-----END PKCS7-----">' +
'<input type="image" src="https://www.paypal.com/en_US/i/btn/btn_donate_LG.gif" border="0" name="submit" alt="PayPal - The safer, easier way to pay online!">' +
'</form>'
'Or support me via:' +
'<iframe src="http://couchpota.to/donate.html" style="border:none; height: 200px;" scrolling="no"></iframe>'
})
);
@@ -119,6 +117,7 @@ var AboutSettingTab = new Class({
var self = this;
var date = new Date(json.version.date * 1000);
self.version_text.set('text', json.version.hash + ' ('+date.toUTCString()+')');
self.updater_type.set('text', json.version.type);
}
});

View File

@@ -31,7 +31,6 @@ window.addEvent('domready', function(){
'IMDB': IMDBAction
,'Trailer': TrailerAction
,'Releases': ReleaseAction
,'Edit': new Class({
Extends: MovieAction,
@@ -74,20 +73,23 @@ window.addEvent('domready', function(){
new Element('option', {
'text': alt.title
}).inject(self.title_select);
if(alt['default'])
self.title_select.set('value', alt.title);
});
Quality.getActiveProfiles().each(function(profile){
var profile_id = profile.id ? profile.id : profile.data.id;
new Element('option', {
'value': profile.id ? profile.id : profile.data.id,
'value': profile_id,
'text': profile.label ? profile.label : profile.data.label
}).inject(self.profile_select);
if(self.movie.profile)
self.profile_select.set('value', profile.id ? profile.id : profile.data.id);
if(self.movie.profile && self.movie.profile.data.id == profile_id)
self.profile_select.set('value', profile_id);
});
}
@@ -170,7 +172,7 @@ window.addEvent('domready', function(){
(e).preventDefault();
if(!self.delete_container){
self.delete_container = new Element('div.delete_container').adopt(
self.delete_container = new Element('div.buttons.delete_container').adopt(
new Element('a.cancel', {
'text': 'Cancel',
'events': {