Browse Source

Downloading of single releases and more

pull/51/merge
Ruud 14 years ago
parent
commit
cb5fc694ab
  1. 14
      couchpotato/core/downloaders/blackhole/main.py
  2. 19
      couchpotato/core/downloaders/sabnzbd/main.py
  3. 23
      couchpotato/core/plugins/base.py
  4. 81
      couchpotato/core/plugins/movie/static/movie.css
  5. 84
      couchpotato/core/plugins/movie/static/movie.js
  6. 4
      couchpotato/core/plugins/movie/static/search.js
  7. 2
      couchpotato/core/plugins/quality/main.py
  8. 55
      couchpotato/core/plugins/release/main.py
  9. 47
      couchpotato/core/plugins/renamer/main.py
  10. 16
      couchpotato/core/plugins/scanner/main.py
  11. 63
      couchpotato/core/plugins/searcher/main.py
  12. 20
      couchpotato/core/providers/base.py
  13. 10
      couchpotato/core/providers/nzb/newzbin/main.py
  14. 14
      couchpotato/core/providers/nzb/newznab/main.py
  15. 4
      couchpotato/core/providers/nzb/nzbindex/main.py
  16. 2
      couchpotato/core/providers/nzb/nzbmatrix/main.py
  17. 2
      couchpotato/core/providers/nzb/nzbs/main.py
  18. 3
      couchpotato/core/settings/model.py
  19. BIN
      couchpotato/static/images/edit.png
  20. 0
      couchpotato/static/images/icon.check.png
  21. 0
      couchpotato/static/images/icon.delete.png
  22. BIN
      couchpotato/static/images/icon.download.png
  23. BIN
      couchpotato/static/images/icon.edit.png
  24. 0
      couchpotato/static/images/icon.imdb.png
  25. 0
      couchpotato/static/images/icon.rating.png
  26. BIN
      couchpotato/static/images/icon.refresh.png
  27. BIN
      couchpotato/static/images/reload.png
  28. 4
      couchpotato/static/scripts/page/wanted.js
  29. 12
      couchpotato/static/style/main.css
  30. 2
      couchpotato/static/style/page/settings.css
  31. 88
      libs/multipartpost.py

14
couchpotato/core/downloaders/blackhole/main.py

@ -27,18 +27,16 @@ class Blackhole(Downloader):
try:
if not os.path.isfile(fullPath):
log.info('Downloading %s to %s.' % (data.get('type'), fullPath))
if isfunction(data.get('download')):
file = data.get('download')()
else:
file = self.urlopen(data.get('url'))
if not file or file == '':
try:
file = data.get('download')(url = data.get('url'), nzb_id = data.get('id'))
with open(fullPath, 'wb') as f:
f.write(file)
except:
log.debug('Failed download file: %s' % data.get('name'))
return False
with open(fullPath, 'wb') as f:
f.write(file)
return True
else:
log.info('File %s already exists.' % fullPath)

19
couchpotato/core/downloaders/sabnzbd/main.py

@ -6,6 +6,7 @@ from urllib import urlencode
import base64
import os
import re
import traceback
log = CPLog(__name__)
@ -37,15 +38,11 @@ class Sabnzbd(Downloader):
params = {
'apikey': self.conf('api_key'),
'cat': self.conf('category'),
'mode': 'addurl',
'name': data.get('url'),
'mode': 'addfile',
'nzbname': '%s%s' % (data.get('name'), self.cpTag(movie)),
}
# sabNzbd complains about "invalid archive file" for newzbin urls
# added using addurl, works fine with addid
if data.get('addbyid'):
params['mode'] = 'addid'
nzb_file = data.get('download')(url = data.get('url'), nzb_id = data.get('id'))
if pp:
params['script'] = pp_script_fn
@ -53,9 +50,9 @@ class Sabnzbd(Downloader):
url = cleanHost(self.conf('host')) + "api?" + urlencode(params)
try:
data = self.urlopen(url)
except Exception, e:
log.error("Unable to connect to SAB: %s" % e)
data = self.urlopen(url, params = {"nzbfile": (params['nzbname'] + ".nzb", nzb_file)}, multipart = True)
except Exception:
log.error("Unable to connect to SAB: %s" % traceback.format_exc())
return False
result = data.strip()
@ -63,7 +60,7 @@ class Sabnzbd(Downloader):
log.error("SABnzbd didn't return anything.")
return False
log.debug("Result text from SAB: " + result)
log.debug("Result text from SAB: " + result[:40])
if result == "ok":
log.info("NZB sent to SAB successfully.")
return True
@ -71,7 +68,7 @@ class Sabnzbd(Downloader):
log.error("Incorrect username/password.")
return False
else:
log.error("Unknown error: " + result)
log.error("Unknown error: " + result[:40])
return False
def buildPp(self, imdb_id):

23
couchpotato/core/plugins/base.py

@ -4,7 +4,9 @@ from couchpotato.core.helpers.variable import getExt
from couchpotato.core.logger import CPLog
from couchpotato.environment import Env
from flask.helpers import send_from_directory
from libs.multipartpost import MultipartPostHandler
from urlparse import urlparse
import cookielib
import glob
import math
import os.path
@ -80,7 +82,7 @@ class Plugin(object):
return False
# http request
def urlopen(self, url, timeout = 10, params = {}, headers = {}):
def urlopen(self, url, timeout = 10, params = {}, headers = {}, multipart = False):
socket.setdefaulttimeout(timeout)
@ -88,15 +90,24 @@ class Plugin(object):
self.wait(host)
try:
log.info('Opening url: %s, params: %s' % (url, params))
data = urllib.urlencode(params) if len(params) > 0 else None
request = urllib2.Request(url, data, headers)
if multipart:
log.info('Opening multipart url: %s, params: %s' % (url, params.iterkeys()))
request = urllib2.Request(url, params, headers)
data = urllib2.urlopen(request).read()
cookies = cookielib.CookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookies), MultipartPostHandler)
data = opener.open(request).read()
else:
log.info('Opening url: %s, params: %s' % (url, params))
data = urllib.urlencode(params) if len(params) > 0 else None
request = urllib2.Request(url, data, headers)
data = urllib2.urlopen(request).read()
except IOError, e:
log.error('Failed opening url, %s: %s' % (url, e))
data = None
raise
self.http_last_use[host] = time.time()

81
couchpotato/core/plugins/movie/static/movie.css

@ -1,4 +1,7 @@
/* @override http://localhost:5000/static/movie_plugin/movie.css */
/* @override
http://localhost:5000/static/movie_plugin/movie.css
http://192.168.1.20:5000/static/movie_plugin/movie.css
*/
.movies {
padding: 20px 0;
@ -79,13 +82,21 @@
float: left;
width: 5%;
padding: 0 0 0 3%;
background: url('../images/rating.png') no-repeat left center;
}
.movies .info .description {
clear: both;
width: 95%;
}
.movies .data .quality span {
padding: 5px;
font-weight: bold;
}
.movies .data .quality .available { color: orange; }
.movies .data .quality .snatched { color: lightgreen; }
.movies .data .actions {
position: absolute;
right: 15px;
@ -96,17 +107,14 @@
.movies .data:hover .action:hover { opacity: 1; }
.movies .data .action {
background: no-repeat center;
background-repeat: no-repeat;
background-position: center;
display: inline-block;
width: 20px;
height: 20px;
padding: 3px;
opacity: 0;
}
.movies .data .action.refresh { background-image: url('../images/reload.png'); }
.movies .data .action.delete { background-image: url('../images/delete.png'); }
.movies .data .action.edit { background-image: url('../images/edit.png'); }
.movies .data .action.imdb { background-image: url('../images/imdb.png'); }
.movies .delete_container {
clear: both;
@ -142,6 +150,65 @@
padding: 2%;
}
.movies .options .releases {
height: 157px;
overflow: auto;
margin: -20px -20px -20px 110px;
padding: 15px 0 5px;
}
.movies .options .releases .item {
border-bottom: 1px solid rgba(255,255,255,0.1);
}
.movies .options .releases .item:last-child { border: 0; }
.movies .options .releases .item:nth-child(even) {
background: rgba(255,255,255,0.05);
}
.movies .options .releases .item:not(.head):hover {
background: rgba(255,255,255,0.03);
}
.movies .options .releases .item > * {
display: inline-block;
padding: 0 5px;
width: 50px;
min-height: 24px;
white-space: nowrap;
text-overflow: ellipsis;
-moz-text-overflow: ellipsis;
text-align: center;
vertical-align: top;
border-left: 1px solid rgba(255, 255, 255, 0.1);
}
.movies .options .releases .item > *:first-child {
border: 0;
}
.movies .options .releases .provider {
width: 120px;
}
.movies .options .releases .name {
width: 420px;
overflow: hidden;
text-align: left;
padding: 0 10px;
}
.movies .options .releases a {
width: 16px !important;
height: 16px;
opacity: 0.8;
}
.movies .options .releases a:hover {
opacity: 1;
}
.movies .options .releases .head > * {
font-weight: bold;
font-size: 14px;
padding-top: 4px;
padding-bottom: 4px;
height: auto;
}
.movies .alph_nav ul {
list-style: none;
padding: 0;

84
couchpotato/core/plugins/movie/static/movie.js

@ -32,7 +32,7 @@ var Movie = new Class({
self.year = new Element('div.year', {
'text': self.data.library.year || 'Unknown'
}),
self.rating = new Element('div.rating', {
self.rating = new Element('div.rating.icon', {
'text': self.data.library.rating
}),
self.description = new Element('div.description', {
@ -47,11 +47,18 @@ var Movie = new Class({
);
self.profile.get('types').each(function(type){
// Check if quality is snatched
var is_snatched = self.data.releases.filter(function(release){
return release.quality_id == type.quality_id && release.status.identifier == 'snatched'
}).pick();
var q = Quality.getQuality(type.quality_id);
new Element('span', {
'text': ' '+q.label
'text': q.label,
'class': is_snatched ? 'snatched' : ''
}).inject(self.quality);
})
});
Object.each(self.options.actions, function(action, key){
self.actions.adopt(
@ -127,7 +134,7 @@ var Movie = new Class({
var MovieAction = new Class({
class_name: 'action',
class_name: 'action icon',
initialize: function(movie){
var self = this;
@ -193,7 +200,7 @@ var ReleaseAction = new Class({
self.id = self.movie.get('identifier');
self.el = new Element('a.releases', {
self.el = new Element('a.releases.icon.download', {
'title': 'Show the releases that are available for ' + self.movie.getTitle(),
'events': {
'click': self.show.bind(self)
@ -211,16 +218,77 @@ var ReleaseAction = new Class({
$(self.movie.thumbnail).clone(),
self.release_container = new Element('div.releases')
).inject(self.movie, 'top');
// Header
new Element('div.item.head').adopt(
new Element('span.name', {'text': 'Release name'}),
new Element('span.quality', {'text': 'Quality'}),
new Element('span.age', {'text': 'Age'}),
new Element('span.score', {'text': 'Score'}),
new Element('span.provider', {'text': 'Provider'})
).inject(self.release_container)
Array.each(self.movie.data.releases, function(release){
p(release);
new Element('div', {
'text': release.title
}).inject(self.release_container)
'class': 'item ' + release.status.identifier
}).adopt(
new Element('span.name', {'text': self.get(release, 'name'), 'title': self.get(release, 'name')}),
new Element('span.quality', {'text': release.quality.label}),
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')}),
new Element('a.download.icon', {
'events': {
'click': function(e){
(e).stop();
self.download(release);
}
}
}),
new Element('a.delete.icon', {
'events': {
'click': function(e){
(e).stop();
self.delete(release);
}
}
})
).inject(self.release_container)
});
}
self.movie.slide('in');
},
download: function(release){
var self = this;
p(release)
Api.request('release.download', {
'data': {
'id': release.id
}
});
},
delete: function(release){
var self = this;
Api.request('release.delete', {
'data': {
'id': release.id
}
})
},
get: function(release, type){
var self = this;
return (release.info.filter(function(info){
return type == info.identifier
}).pick() || {}).value
}
});

4
couchpotato/core/plugins/movie/static/search.js

@ -29,7 +29,7 @@ Block.Search = new Class({
}).adopt(
new Element('div.pointer'),
self.results = new Element('div.results')
).fade('hide')
).hide()
);
self.spinner = new Spinner(self.result_container);
@ -52,7 +52,7 @@ Block.Search = new Class({
if(self.hidden == bool) return;
self.result_container.fade(bool ? 0 : 1)
self.result_container[bool ? 'hide' : 'show']();
if(bool){
History.removeEvent('change', self.hideResults.bind(self, !bool));

2
couchpotato/core/plugins/quality/main.py

@ -13,7 +13,7 @@ log = CPLog(__name__)
class QualityPlugin(Plugin):
qualities = [
{'identifier': 'bd50', 'size': (15000, 60000), 'label': 'BR-Disk', 'width': 1920, 'alternative': ['bd25'], 'allow': ['1080p'], 'ext':[], 'tags': []},
{'identifier': 'bd50', 'size': (15000, 60000), 'label': 'BR-Disk', 'width': 1920, 'alternative': ['bd25'], 'allow': ['1080p'], 'ext':[], 'tags': ['bdmv', 'certificate']},
{'identifier': '1080p', 'size': (5000, 20000), 'label': '1080P', 'width': 1920, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts'], 'tags': ['x264', 'h264', 'bluray']},
{'identifier': '720p', 'size': (3500, 10000), 'label': '720P', 'width': 1280, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts'], 'tags': ['x264', 'h264', 'bluray']},
{'identifier': 'brrip', 'size': (700, 7000), 'label': 'BR-Rip', 'alternative': ['bdrip'], 'allow': ['720p'], 'ext':['avi']},

55
couchpotato/core/plugins/release/main.py

@ -1,8 +1,10 @@
from couchpotato import get_session
from couchpotato.api import addApiView
from couchpotato.core.event import fireEvent, addEvent
from couchpotato.core.helpers.request import getParam, jsonified
from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin
from couchpotato.core.settings.model import File, Release, Movie
from couchpotato.core.settings.model import File, Release as Relea, Movie
from sqlalchemy.sql.expression import and_, or_
log = CPLog(__name__)
@ -13,6 +15,8 @@ class Release(Plugin):
def __init__(self):
addEvent('release.add', self.add)
addApiView('release.download', self.download)
def add(self, group):
db = get_session()
@ -30,22 +34,22 @@ class Release(Plugin):
db.add(movie)
db.commit()
# Add release
# Add Release
snatched_status = fireEvent('status.get', 'snatched', single = True)
release = db.query(Release).filter(
rel = db.query(Relea).filter(
or_(
Release.identifier == identifier,
and_(Release.identifier.startswith(group['library']['identifier'], Release.status_id == snatched_status.get('id')))
Relea.identifier == identifier,
and_(Relea.identifier.startswith(group['library']['identifier'], Relea.status_id == snatched_status.get('id')))
)
).first()
if not release:
release = Release(
if not rel:
rel = Relea(
identifier = identifier,
movie = movie,
quality_id = group['meta_data']['quality'].get('id'),
status_id = done_status.get('id')
)
db.add(release)
db.add(rel)
db.commit()
# Add each file type
@ -54,10 +58,10 @@ class Release(Plugin):
added_file = self.saveFile(file, type = type, include_media_info = type is 'movie')
try:
added_file = db.query(File).filter_by(id = added_file.get('id')).one()
release.files.append(added_file)
Relea.files.append(added_file)
db.commit()
except Exception, e:
log.debug('Failed to attach "%s" to release: %s' % (file, e))
log.debug('Failed to attach "%s" to Relea: %s' % (file, e))
db.remove()
@ -73,3 +77,34 @@ class Release(Plugin):
# Check database and update/insert if necessary
return fireEvent('file.add', path = file, part = self.getPartNumber(file), type = self.file_types[type], properties = properties, single = True)
def download(self):
db = get_session()
id = getParam('id')
rel = db.query(Relea).filter_by(id = id).first()
if rel:
item = {}
for info in rel.info:
item[info.identifier] = info.value
# Get matching provider
provider = fireEvent('provider.belongs_to', item['url'], single = True)
item['download'] = provider.download
fireEvent('searcher.download', data = item, movie = rel.movie.to_dict({
'profile': {'types': {'quality': {}}},
'releases': {'status': {}, 'quality': {}},
'library': {'titles': {}, 'files':{}},
'files': {}
}))
return jsonified({
'success': True
})
else:
log.error('Couldn\'t find release with id: %s' % id)
return jsonified({
'success': False
})

47
couchpotato/core/plugins/renamer/main.py

@ -4,7 +4,7 @@ from couchpotato.core.helpers.encoding import toUnicode
from couchpotato.core.helpers.variable import getExt
from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin
from couchpotato.core.settings.model import Library
from couchpotato.core.settings.model import Library, Movie
import os.path
import re
import shutil
@ -134,8 +134,18 @@ class Renamer(Plugin):
# Move DVD files (no structure renaming)
if group['is_dvd'] and file_type is 'movie':
structure_dir = file.split(group['dirname'])[-1].lstrip(os.path.sep)
rename_files[file] = os.path.join(destination, final_folder_name, structure_dir)
found = False
for top_dir in ['video_ts', 'audio_ts', 'bdmv', 'certificate']:
has_string = file.lower().find(os.path.sep + top_dir + os.path.sep)
if has_string >= 0:
structure_dir = file[has_string:].lstrip(os.path.sep)
rename_files[file] = os.path.join(destination, final_folder_name, structure_dir)
found = True
break
if not found:
log.error('Could not determin dvd structure for: %s' % file)
# Do rename others
else:
rename_files[file] = os.path.join(destination, final_folder_name, final_file_name)
@ -162,15 +172,41 @@ class Renamer(Plugin):
# Before renaming, remove the lower quality files
db = get_session()
library = db.query(Library).filter_by(identifier = group['library']['identifier']).first()
done_status = fireEvent('status.get', 'done', single = True)
active_status = fireEvent('status.get', 'active', single = True)
for movie in library.movies:
# Mark movie "done" onces it found the quality with the finish check
try:
if movie.status_id == active_status.get('id'):
for type in movie.profile.types:
if type.quality_id == group['meta_data']['quality']['id'] and type.finish:
movie.status_id = done_status.get('id')
db.commit()
except Exception, e:
log.error('Failed marking movie finished: %s %s' % (e, traceback.format_exc()))
# Go over current movie releases
for release in movie.releases:
# This is where CP removes older, lesser quality releases
if release.quality.order > group['meta_data']['quality']['order']:
log.info('Removing older release for %s, with quality %s' % (movie.library.titles[0].title, release.quality.label))
for file in release.files:
log.info('Removing (not really) "%s"' % file.path)
# When a release already exists
elif release.status_id is done_status.get('id'):
# Same quality, but still downloaded, so maybe repack/proper/unrated/directors cut etc
if release.quality.order is group['meta_data']['quality']['order']:
log.info('Same quality release already exists for %s, with quality %s. Assuming repack.' % (movie.library.titles[0].title, release.quality.label))
# Downloaded a lower quality, rename the newly downloaded files/folder to exclude them from scan
else:
log.info('Better quality release already exists for %s, with quality %s' % (movie.library.titles[0].title, release.quality.label))
@ -190,10 +226,7 @@ class Renamer(Plugin):
break
for file in release.files:
log.info('Removing (not really) "%s"' % file.path)
# Rename
# Rename all files marked
for src in rename_files:
if rename_files[src]:

16
couchpotato/core/plugins/scanner/main.py

@ -23,11 +23,11 @@ class Scanner(Plugin):
'trailer': 1048576, # 1MB
}
ignored_in_path = ['_unpack', '_failed_', '_unknown_', '_exists_', '.appledouble', '.appledb', '.appledesktop', os.path.sep + '._', '.ds_store', 'cp.cpnfo'] #unpacking, smb-crap, hidden files
ignore_names = ['extract', 'extracting', 'extracted', 'movie', 'movies', 'film', 'films', 'download', 'downloads', 'video_ts', 'audio_ts']
ignore_names = ['extract', 'extracting', 'extracted', 'movie', 'movies', 'film', 'films', 'download', 'downloads', 'video_ts', 'audio_ts', 'bdmv', 'certificate']
extensions = {
'movie': ['mkv', 'wmv', 'avi', 'mpg', 'mpeg', 'mp4', 'm2ts', 'iso', 'img'],
'dvd': ['vts_*', 'vob'],
'nfo': ['nfo', 'txt', 'tag'],
'nfo': ['nfo', 'nfo-orig', 'txt', 'tag'],
'subtitle': ['sub', 'srt', 'ssa', 'ass'],
'subtitle_extra': ['idx'],
'trailer': ['mov', 'mp4', 'flv']
@ -210,13 +210,13 @@ class Scanner(Plugin):
group['parentdir'] = os.path.dirname(movie_file)
group['dirname'] = None
folders = group['parentdir'].replace(folder, '').split(os.path.sep)
folders.reverse()
folder_names = group['parentdir'].replace(folder, '').split(os.path.sep)
folder_names.reverse()
# Try and get a proper dirname, so no "A", "Movie", "Download" etc
for folder in folders:
if folder.lower() not in self.ignore_names and len(folder) > 2:
group['dirname'] = folder
for folder_name in folder_names:
if folder_name.lower() not in self.ignore_names and len(folder_name) > 2:
group['dirname'] = folder_name
break
break
@ -426,7 +426,7 @@ class Scanner(Plugin):
if list(set(file.lower().split(os.path.sep)) & set(['video_ts', 'audio_ts'])):
return True
for needle in ['vts_', 'video_ts', 'audio_ts']:
for needle in ['vts_', 'video_ts', 'audio_ts', 'bdmv', 'certificate']:
if needle in file.lower():
return True

63
couchpotato/core/plugins/searcher/main.py

@ -7,6 +7,7 @@ from couchpotato.core.plugins.base import Plugin
from couchpotato.core.settings.model import Movie, Release, ReleaseInfo
from couchpotato.environment import Env
import re
import traceback
log = CPLog(__name__)
@ -17,6 +18,7 @@ class Searcher(Plugin):
addEvent('searcher.all', self.all)
addEvent('searcher.single', self.single)
addEvent('searcher.correct_movie', self.correctMovie)
addEvent('searcher.download', self.download)
# Schedule cronjob
fireEvent('schedule.cron', 'searcher.all', self.all, day = self.conf('cron_day'), hour = self.conf('cron_hour'), minute = self.conf('cron_minute'))
@ -34,7 +36,7 @@ class Searcher(Plugin):
for movie in movies:
self.single(movie.to_dict(deep = {
self.single(movie.to_dict({
'profile': {'types': {'quality': {}}},
'releases': {'status': {}, 'quality': {}},
'library': {'titles': {}, 'files':{}},
@ -47,11 +49,8 @@ class Searcher(Plugin):
def single(self, movie):
downloaded_status = fireEvent('status.get', 'downloaded', single = True)
available_status = fireEvent('status.get', 'available', single = True)
snatched_status = fireEvent('status.get', 'snatched', single = True)
successful = False
for type in movie['profile']['types']:
has_better_quality = 0
@ -85,37 +84,19 @@ class Searcher(Plugin):
db.commit()
for info in nzb:
rls_info = ReleaseInfo(
identifier = info,
value = nzb[info]
)
rls.info.append(rls_info)
try:
rls_info = ReleaseInfo(
identifier = info,
value = nzb[info]
)
rls.info.append(rls_info)
except Exception:
log.debug('Couldn\'t add %s to ReleaseInfo: %s' % (info, traceback.format_exc()))
db.commit()
for nzb in sorted_results:
successful = fireEvent('download', data = nzb, movie = movie, single = True)
if successful:
# Mark release as snatched
db = get_session()
rls = db.query(Release).filter_by(identifier = md5(nzb['url'])).first()
rls.status_id = snatched_status.get('id')
db.commit()
# Mark movie snatched if quality is finish-checked
if type['finish']:
mvie = db.query(Movie).filter_by(id = movie['id']).first()
mvie.status_id = snatched_status.get('id')
db.commit()
log.info('Downloading of %s successful.' % nzb.get('name'))
fireEvent('movie.snatched', message = 'Downloading of %s successful.' % nzb.get('name'), data = rls.to_dict())
return True
return False
return self.download(data = nzb, movie = movie)
else:
log.info('Better quality (%s) already available or snatched for %s' % (type['quality']['label'], default_title))
break
@ -126,6 +107,26 @@ class Searcher(Plugin):
return False
def download(self, data, movie):
snatched_status = fireEvent('status.get', 'snatched', single = True)
successful = fireEvent('download', data = data, movie = movie, single = True)
if successful:
# Mark release as snatched
db = get_session()
rls = db.query(Release).filter_by(identifier = md5(data['url'])).first()
rls.status_id = snatched_status.get('id')
db.commit()
log.info('Downloading of %s successful.' % data.get('name'))
fireEvent('movie.snatched', message = 'Downloading of %s successful.' % data.get('name'), data = rls.to_dict())
return True
return False
def correctMovie(self, nzb = {}, movie = {}, quality = {}, **kwargs):

20
couchpotato/core/providers/base.py

@ -2,7 +2,6 @@ from couchpotato.core.event import addEvent
from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin
from couchpotato.environment import Env
from urllib2 import URLError
from urlparse import urlparse
import re
import time
@ -60,6 +59,20 @@ class YarrProvider(Provider):
sizeMb = ['mb', 'mib']
sizeKb = ['kb', 'kib']
def __init__(self):
addEvent('provider.belongs_to', self.belongsTo)
def belongsTo(self, url, host = None):
try:
hostname = urlparse(url).hostname
download_url = host if host else self.urls['download']
if hostname in download_url:
return self
except:
log.debug('Url % s doesn\'t belong to %s' % (url, self.getName()))
return
def parseSize(self, size):
sizeRaw = size.lower()
@ -96,11 +109,16 @@ class NZBProvider(YarrProvider):
type = 'nzb'
def __init__(self):
super(NZBProvider, self).__init__()
addEvent('provider.nzb.search', self.search)
addEvent('provider.yarr.search', self.search)
addEvent('provider.nzb.feed', self.feed)
def download(self, url = '', nzb_id = ''):
return self.urlopen(url)
def feed(self):
return []

10
couchpotato/core/providers/nzb/newzbin/main.py

@ -13,10 +13,9 @@ log = CPLog(__name__)
class Newzbin(NZBProvider, RSS):
urls = {
'search': 'https://www.newzbin.com/search/',
'download': 'http://www.newzbin.com/api/dnzb/',
'search': 'https://www.newzbin.com/search/',
}
searchUrl = 'https://www.newzbin.com/search/'
format_ids = {
2: ['scr'],
@ -36,7 +35,7 @@ class Newzbin(NZBProvider, RSS):
def search(self, movie, quality):
results = []
if self.isDisabled() or not self.isAvailable(self.searchUrl):
if self.isDisabled() or not self.isAvailable(self.urls['search']):
return results
format_id = self.getFormatId(type)
@ -97,11 +96,12 @@ class Newzbin(NZBProvider, RSS):
new = {
'id': id,
'type': 'nzb',
'provider': self.getName(),
'name': title,
'age': self.calculateAge(int(time.mktime(parse(date).timetuple()))),
'size': self.parseSize(size),
'url': str(self.getTextElement(nzb, '{%s}nzb' % REPORT_NS)),
'download': lambda: self.download(id),
'download': self.download,
'detail_url': str(self.getTextElement(nzb, 'link')),
'description': self.getTextElement(nzb, "description"),
'check_nzb': False,
@ -121,7 +121,7 @@ class Newzbin(NZBProvider, RSS):
return results
def download(self, nzb_id):
def download(self, url = '', nzb_id = ''):
try:
log.info('Download nzb from newzbin, report id: %s ' % nzb_id)

14
couchpotato/core/providers/nzb/newznab/main.py

@ -5,6 +5,7 @@ from couchpotato.core.logger import CPLog
from couchpotato.core.providers.base import NZBProvider
from dateutil.parser import parse
from urllib import urlencode
from urlparse import urlparse
import time
import xml.etree.ElementTree as XMLTree
@ -130,11 +131,13 @@ class Newznab(NZBProvider, RSS):
id = self.getTextElement(nzb, "guid").split('/')[-1:].pop()
new = {
'id': id,
'provider': self.getName(),
'type': 'nzb',
'name': self.getTextElement(nzb, "title"),
'age': self.calculateAge(int(time.mktime(parse(date).timetuple()))),
'size': int(size) / 1024 / 1024,
'url': (self.getUrl(host['host'], self.urls['download']) % id) + self.getApiExt(host),
'download': self.download,
'detail_url': (self.getUrl(host['host'], self.urls['detail']) % id) + self.getApiExt(host),
'content': self.getTextElement(nzb, "description"),
}
@ -173,6 +176,17 @@ class Newznab(NZBProvider, RSS):
return list
def belongsTo(self, url):
hosts = self.getHosts()
for host in hosts:
result = super(Newznab, self).belongsTo(url, host = host['host'])
if result:
return result
return
def getUrl(self, host, type):
return cleanHost(host) + 'api?t=' + type

4
couchpotato/core/providers/nzb/nzbindex/main.py

@ -14,7 +14,7 @@ log = CPLog(__name__)
class NzbIndex(NZBProvider, RSS):
urls = {
'download': 'http://www.nzbindex.nl/download/%s/%s',
'download': 'http://www.nzbindex.nl/download/',
'api': 'http://www.nzbindex.nl/rss/', #http://www.nzbindex.nl/rss/?q=due+date+720p&age=1000&sort=agedesc&minsize=3500&maxsize=10000
}
@ -63,10 +63,12 @@ class NzbIndex(NZBProvider, RSS):
new = {
'id': id,
'type': 'nzb',
'provider': self.getName(),
'name': self.getTextElement(nzb, "title"),
'age': self.calculateAge(int(time.mktime(parse(self.getTextElement(nzb, "pubDate")).timetuple()))),
'size': enclosure['length'],
'url': enclosure['url'],
'download': self.download,
'detail_url': enclosure['url'].replace('/download/', '/release/'),
'description': self.getTextElement(nzb, "description"),
'check_nzb': True,

2
couchpotato/core/providers/nzb/nzbmatrix/main.py

@ -81,10 +81,12 @@ class NZBMatrix(NZBProvider, RSS):
new = {
'id': id,
'type': 'nzb',
'provider': self.getName(),
'name': title,
'age': self.calculateAge(int(time.mktime(parse(date).timetuple()))),
'size': self.parseSize(size),
'url': self.urls['download'] % id + self.getApiExt(),
'download': self.download,
'detail_url': self.urls['detail'] % id,
'description': self.getTextElement(nzb, "description"),
'check_nzb': True,

2
couchpotato/core/providers/nzb/nzbs/main.py

@ -71,10 +71,12 @@ class Nzbs(NZBProvider, RSS):
new = {
'id': id,
'type': 'nzb',
'provider': self.getName(),
'name': self.getTextElement(nzb, "title"),
'age': self.calculateAge(int(time.mktime(parse(self.getTextElement(nzb, "pubDate")).timetuple()))),
'size': self.parseSize(self.getTextElement(nzb, "description").split('</a><br />')[1].split('">')[1]),
'url': self.urls['download'] % (id, self.getApiExt()),
'download': self.download,
'detail_url': self.urls['detail'] % id,
'description': self.getTextElement(nzb, "description"),
'check_nzb': True,

3
couchpotato/core/settings/model.py

@ -47,6 +47,9 @@ class Library(Entity):
files = ManyToMany('File')
info = OneToMany('LibraryInfo')
def title(self):
return self.titles[0]['title']
class LibraryInfo(Entity):
""""""

BIN
couchpotato/static/images/edit.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1015 B

0
couchpotato/static/images/check.png → couchpotato/static/images/icon.check.png

Before

Width:  |  Height:  |  Size: 451 B

After

Width:  |  Height:  |  Size: 451 B

0
couchpotato/static/images/delete.png → couchpotato/static/images/icon.delete.png

Before

Width:  |  Height:  |  Size: 228 B

After

Width:  |  Height:  |  Size: 228 B

BIN
couchpotato/static/images/icon.download.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 B

BIN
couchpotato/static/images/icon.edit.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 624 B

0
couchpotato/static/images/imdb.png → couchpotato/static/images/icon.imdb.png

Before

Width:  |  Height:  |  Size: 159 B

After

Width:  |  Height:  |  Size: 159 B

0
couchpotato/static/images/rating.png → couchpotato/static/images/icon.rating.png

Before

Width:  |  Height:  |  Size: 283 B

After

Width:  |  Height:  |  Size: 283 B

BIN
couchpotato/static/images/icon.refresh.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 674 B

BIN
couchpotato/static/images/reload.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 726 B

4
couchpotato/static/scripts/page/wanted.js

@ -27,7 +27,7 @@ var MovieActions = {};
MovieActions.Wanted = {
'IMBD': IMDBAction
//,'releases': ReleaseAction
,'releases': ReleaseAction
,'Edit': new Class({
@ -207,7 +207,6 @@ MovieActions.Wanted = {
self.chain(
function(){
$(movie).mask().addClass('loading');
self.callChain();
},
function(){
@ -236,6 +235,5 @@ MovieActions.Wanted = {
MovieActions.Snatched = {
'IMBD': IMDBAction
,'Releases': ReleaseAction
,'Delete': MovieActions.Wanted.Delete
};

12
couchpotato/static/style/main.css

@ -137,10 +137,18 @@ form {
}
/*** Icons ***/
.icon.delete {
background: url('../images/delete.png') no-repeat;
.icon {
display: inline-block;
background: center no-repeat;
}
.icon.delete { background-image: url('../images/icon.delete.png'); }
.icon.download { background-image: url('../images/icon.download.png'); }
.icon.edit { background-image: url('../images/icon.edit.png'); }
.icon.check { background-image: url('../images/icon.check.png'); }
.icon.folder { background-image: url('../images/icon.folder.png'); }
.icon.imdb { background-image: url('../images/icon.imdb.png'); }
.icon.refresh { background-image: url('../images/icon.refresh.png'); }
.icon.rating { background-image: url('../images/icon.rating.png'); }
/*** Navigation ***/
.header {

2
couchpotato/static/style/page/settings.css

@ -103,7 +103,7 @@
border: 0;
}
.page.settings .ctrlHolder.save_success:not(:first-child) {
background: url('../../images/check.png') no-repeat 7px center;
background: url('../../images/icon.check.png') no-repeat 7px center;
}
.page.settings .ctrlHolder:last-child { border: none; }
.page.settings .ctrlHolder:hover { background-color: rgba(255,255,255,0.05); }

88
libs/multipartpost.py

@ -0,0 +1,88 @@
#!/usr/bin/python
####
# 06/2010 Nic Wolfe <nic@wolfeden.ca>
# 02/2006 Will Holcomb <wholcomb@gmail.com>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
import urllib
import urllib2
import mimetools, mimetypes
import os, sys
# Controls how sequences are uncoded. If true, elements may be given multiple values by
# assigning a sequence.
doseq = 1
class MultipartPostHandler(urllib2.BaseHandler):
handler_order = urllib2.HTTPHandler.handler_order - 10 # needs to run first
def http_request(self, request):
data = request.get_data()
if data is not None and type(data) != str:
v_files = []
v_vars = []
try:
for(key, value) in data.items():
if type(value) in (file, list, tuple):
v_files.append((key, value))
else:
v_vars.append((key, value))
except TypeError:
systype, value, traceback = sys.exc_info()
raise TypeError, "not a valid non-string sequence or mapping object", traceback
if len(v_files) == 0:
data = urllib.urlencode(v_vars, doseq)
else:
boundary, data = MultipartPostHandler.multipart_encode(v_vars, v_files)
contenttype = 'multipart/form-data; boundary=%s' % boundary
if(request.has_header('Content-Type')
and request.get_header('Content-Type').find('multipart/form-data') != 0):
print "Replacing %s with %s" % (request.get_header('content-type'), 'multipart/form-data')
request.add_unredirected_header('Content-Type', contenttype)
request.add_data(data)
return request
@staticmethod
def multipart_encode(vars, files, boundary = None, buffer = None):
if boundary is None:
boundary = mimetools.choose_boundary()
if buffer is None:
buffer = ''
for(key, value) in vars:
buffer += '--%s\r\n' % boundary
buffer += 'Content-Disposition: form-data; name="%s"' % key
buffer += '\r\n\r\n' + value + '\r\n'
for(key, fd) in files:
# allow them to pass in a file or a tuple with name & data
if type(fd) == file:
name_in = fd.name
fd.seek(0)
data_in = fd.read()
elif type(fd) in (tuple, list):
name_in, data_in = fd
filename = os.path.basename(name_in)
contenttype = mimetypes.guess_type(filename)[0] or 'application/octet-stream'
buffer += '--%s\r\n' % boundary
buffer += 'Content-Disposition: form-data; name="%s"; filename="%s"\r\n' % (key, filename)
buffer += 'Content-Type: %s\r\n' % contenttype
# buffer += 'Content-Length: %s\r\n' % file_size
buffer += '\r\n' + data_in + '\r\n'
buffer += '--%s--\r\n\r\n' % boundary
return boundary, buffer
https_request = http_request
Loading…
Cancel
Save