Compare commits

..

98 Commits

Author SHA1 Message Date
Ruud 51e747049d One up 2013-01-07 23:10:42 +01:00
Ruud 0582f7d694 Urlencode spotweb id. fix #1213 2013-01-07 23:10:06 +01:00
Ruud fa7cac7538 Merge branch 'refs/heads/develop' into desktop 2013-01-07 22:41:55 +01:00
Ruud 9a314cfbc4 One up 2012-12-29 00:03:45 +01:00
Ruud 5941d0bf77 Add version to update url 2012-12-29 00:03:36 +01:00
Ruud d326c1c25c Merge branch 'refs/heads/master' into desktop
Conflicts:
	version.py
2012-12-28 23:31:08 +01:00
Ruud 7e6234298d Merge branch 'refs/heads/develop' 2012-12-28 23:25:40 +01:00
Ruud d4da206f93 Merge branch 'refs/heads/develop' 2012-12-22 16:33:47 +01:00
Ruud 985a168724 Merge branch 'refs/heads/develop' 2012-12-21 23:18:00 +01:00
Ruud 173c6194ed Merge branch 'refs/heads/develop' 2012-12-19 11:12:26 +01:00
Ruud bcd23ad10c Merge branch 'refs/heads/develop' 2012-12-17 15:13:00 +01:00
Ruud 898e6f487d Merge branch 'refs/heads/develop' 2012-12-16 23:52:06 +01:00
Ruud 96472a9a8f One up 2012-12-16 23:51:58 +01:00
Ruud 27252561e2 Merge branch 'refs/heads/develop' into desktop 2012-12-16 23:51:24 +01:00
Ruud 6618c3927c Merge branch 'refs/heads/develop' 2012-12-11 23:15:06 +01:00
Ruud c9e732651f One up 2012-12-01 12:16:58 +01:00
Ruud 7849e7170d Uninstall only create files, no wildcard *.* 2012-12-01 12:16:51 +01:00
Ruud 087894eb4e Merge branch 'refs/heads/develop' into desktop
Conflicts:
	version.py
2012-12-01 11:50:08 +01:00
Ruud 4b58b40226 Merge branch 'refs/heads/develop' 2012-12-01 11:48:54 +01:00
Ruud 3ecc826629 Merge branch 'refs/heads/develop'
Conflicts:
	version.py
2012-11-11 22:06:48 +01:00
Ruud 25f1b8c7a7 Fedora init fix #1009 2012-11-02 18:32:15 +01:00
Ruud e71da1f14d Use proper description for binary build. fix #1005 2012-11-02 18:24:13 +01:00
Ruud 938b14ba18 One up installer 2012-10-29 20:45:17 +01:00
Ruud d6522d8f38 One up installer 2012-10-27 18:49:44 +02:00
Ruud 78eab890e7 Merge branch 'refs/heads/develop' into desktop 2012-10-27 18:25:36 +02:00
Ruud 1a56191f83 Don't unzip 2012-10-27 18:22:50 +02:00
Ruud 41c0f34d95 Properly restart 2012-10-27 18:22:40 +02:00
Ruud 37bf205d7a Merge branch 'refs/heads/develop' into desktop
Conflicts:
	version.py
2012-10-27 11:56:57 +02:00
Ruud 32fe3796e4 Merge branch 'refs/heads/develop' 2012-10-26 22:22:47 +02:00
Ruud 359d1aaafa Merge branch 'refs/heads/develop' 2012-10-26 14:54:12 +02:00
Ruud fb5d336351 Merge branch 'refs/heads/develop' 2012-10-26 14:36:04 +02:00
Ruud eb30dff986 Merge branch 'refs/heads/develop' 2012-10-13 00:00:44 +02:00
Ruud 9312336962 Merge branch 'refs/heads/develop' 2012-09-24 09:36:59 +02:00
Ruud aa1fa3eb9a Add description 2012-09-19 15:42:33 +02:00
Ruud 0e2f8a612c Extract zip after build, for testing 2012-09-19 15:29:07 +02:00
Ruud ade4338ea6 Merge branch 'refs/heads/develop' 2012-09-16 21:32:16 +02:00
Ruud 55b20324c0 Merge branch 'refs/heads/develop' 2012-09-16 12:36:48 +02:00
Ruud 465e7b2abc Merge branch 'refs/heads/develop' into desktop 2012-09-16 12:36:17 +02:00
Ruud 578fb45785 Installer 1 up 2012-09-16 11:35:56 +02:00
Ruud c0fb28301d Merge branch 'refs/heads/develop'
Conflicts:
	version.py
2012-09-16 10:46:39 +02:00
Ruud 96995bbbe5 Merge branch 'refs/heads/develop' into desktop
Conflicts:
	version.py
2012-09-16 10:45:19 +02:00
Ruud 4cfdafebbc Merge branch 'refs/heads/develop' into desktop 2012-09-14 13:15:47 +02:00
Ruud f9c2503f81 Merge branch 'refs/heads/develop' 2012-09-14 13:15:35 +02:00
Ruud b97acb8ef5 Merge branch 'refs/heads/develop' into desktop 2012-09-14 13:08:19 +02:00
Ruud 5b4cdf05b1 Merge branch 'refs/heads/develop' 2012-09-14 13:06:56 +02:00
Ruud d68d2dfdb6 Updated installer 2012-09-09 21:48:38 +02:00
Ruud 39b269a454 Merge branch 'refs/heads/develop' into desktop 2012-09-09 17:32:47 +02:00
Ruud ac081d3e10 Getting ready for build 2012-09-09 17:28:23 +02:00
Ruud 5d4efb60cf Merge branch 'refs/heads/develop' into desktop 2012-09-08 16:01:49 +02:00
Ruud 6f25a6bdfd Merge branch 'refs/heads/develop' 2012-09-03 10:32:09 +02:00
Ruud 23427e95f7 Merge branch 'refs/heads/develop' 2012-08-26 23:09:51 +02:00
Ruud cc408b980c Merge branch 'refs/heads/develop' into desktop
Conflicts:
	couchpotato/core/_base/updater/main.py
2012-08-05 16:18:35 +02:00
Ruud 90a09e573b Merge branch 'refs/heads/develop'
Conflicts:
	couchpotato/core/_base/updater/main.py
2012-08-05 16:15:53 +02:00
Ruud e1d7440b9d Wrong branch in master 2012-07-15 00:23:44 +02:00
Ruud 59590b3ac9 Merge branch 'refs/heads/develop' into desktop
Conflicts:
	couchpotato/core/_base/updater/main.py
2012-07-14 00:35:00 +02:00
Ruud ff759dacf3 Merge branch 'refs/heads/develop' into desktop
Conflicts:
	couchpotato/core/_base/updater/main.py
2012-07-11 22:43:45 +02:00
Ruud a328e44130 Merge branch 'desktop' of github.com:RuudBurger/CouchPotatoServer into desktop 2012-05-15 23:23:56 +02:00
Ruud 7924cac5f9 Update installer version 2012-05-15 23:21:24 +02:00
Ruud 1cef3b0c93 remove --nogit tag 2012-05-15 23:21:24 +02:00
Ruud 3cd59edc8b Import errors
File icon
2012-05-15 23:21:24 +02:00
Ruud 0d624af01d Working PNG 2012-05-15 23:21:24 +02:00
Ruud a09132570c Change branch to desktop 2012-05-15 23:21:14 +02:00
Ruud ee3fc38432 Better setup 2012-05-15 23:21:14 +02:00
Ruud dbf0192c8e Inno setup, start 2012-05-15 23:21:14 +02:00
Ruud 6962cfc3f5 new Desktop runner 2012-05-15 23:21:14 +02:00
Ruud e096ec3b5b Desktop files 2012-05-15 23:20:05 +02:00
Ruud b30a74ae0c Merge branch 'refs/heads/develop' into desktop 2012-05-15 23:15:17 +02:00
Ruud 978eeb16c9 Update installer version 2012-05-15 23:14:20 +02:00
Ruud e5c9d91657 Merge branch 'refs/heads/develop' into desktop 2012-05-15 22:27:22 +02:00
Ruud fa81c3a07a Merge branch 'refs/heads/develop' into desktop
Conflicts:
	version.py
2012-05-14 22:00:02 +02:00
Ruud 9cdd520d41 Merge branch 'refs/heads/develop' into desktop 2012-05-14 20:22:55 +02:00
Ruud 55d7898771 Merge branch 'refs/heads/develop' into desktop 2012-05-13 12:56:45 +02:00
Ruud b8256bef97 Merge branch 'refs/heads/develop' into desktop 2012-05-12 00:35:52 +02:00
Ruud 5be9dc0b4a Merge branch 'refs/heads/develop' into desktop 2012-05-09 22:20:53 +02:00
Ruud 7d0be0cefb remove --nogit tag 2012-05-07 22:55:54 +02:00
Ruud f7ce1edb13 Merge branch 'refs/heads/develop' into desktop 2012-05-07 22:44:01 +02:00
Ruud 5ad9280b60 Merge branch 'refs/heads/develop' into desktop 2012-05-07 22:27:55 +02:00
Ruud 2b353f1b20 Merge branch 'refs/heads/develop' into desktop 2012-05-04 17:29:15 +02:00
Ruud 75ab90b87b Merge branch 'refs/heads/develop' into desktop 2012-05-02 21:40:19 +02:00
Ruud 0219296120 Import errors
File icon
2012-05-02 21:34:45 +02:00
Ruud 20032b3a31 Working PNG 2012-05-01 07:35:44 +02:00
Ruud ea9e9a8c90 Updater base 2012-05-01 07:35:27 +02:00
Ruud f7b0ee145b Change branch to desktop 2012-04-30 21:37:04 +02:00
Ruud cc866738ee Merge branch 'refs/heads/develop' into desktop 2012-04-30 21:32:56 +02:00
Ruud eadccf6e33 Merge branch 'refs/heads/develop' into desktop 2012-04-29 00:00:25 +02:00
Ruud b70b66e567 Merge branch 'refs/heads/develop' into desktop 2012-04-28 23:14:59 +02:00
Ruud 5b6792dc20 Merge branch 'refs/heads/develop' into desktop
Conflicts:
	CouchPotato.py
	couchpotato/core/plugins/renamer/main.py
	couchpotato/core/plugins/trailer/__init__.py
2012-04-07 21:35:36 +02:00
Ruud f498e7343a Better setup 2012-02-25 01:48:58 +01:00
Ruud 6962f441e6 Inno setup, start 2012-02-21 18:50:34 +01:00
Ruud 1def62b1b1 new Desktop runner 2012-02-19 17:13:37 +01:00
Ruud a4a4a6a185 Merge branch 'refs/heads/develop' into desktop
Conflicts:
	CouchPotato.py
2012-02-19 13:14:56 +01:00
Ruud d4c9469c1a Remove nfo when not renaming as .orig.nfo 2012-02-19 12:53:55 +01:00
Ruud 3e2d4c5d7b Initial trailer support 2012-02-19 12:48:54 +01:00
Ruud d03f711d69 kwargs in file.download for urlopen 2012-02-19 12:45:22 +01:00
Ruud 44dd8d9b96 Merge lists, not overwrite 2012-02-19 12:37:25 +01:00
Ruud 549a3be0d8 Merge branch 'refs/heads/develop' into desktop 2012-02-12 00:10:56 +01:00
Ruud 1bb2edf8ec Merge branch 'refs/heads/develop' into desktop 2012-02-11 23:33:14 +01:00
Ruud 84c6f36315 Desktop files 2012-02-11 23:06:14 +01:00
186 changed files with 2978 additions and 5194 deletions
+2 -1
View File
@@ -62,6 +62,7 @@ class Loader(object):
self.log.logger.addHandler(hdlr)
def addSignals(self):
signal.signal(signal.SIGINT, self.onExit)
signal.signal(signal.SIGTERM, lambda signum, stack_frame: sys.exit(1))
@@ -73,7 +74,7 @@ class Loader(object):
def onExit(self, signal, frame):
from couchpotato.core.event import fireEvent
fireEvent('app.shutdown', single = True)
fireEvent('app.crappy_shutdown', single = True)
def run(self):
+231
View File
@@ -0,0 +1,231 @@
from esky.util import appdir_from_executable #@UnresolvedImport
from threading import Thread
from version import VERSION
from wx.lib.softwareupdate import SoftwareUpdate
import os
import sys
import time
import webbrowser
import wx
# Include proper dirs
if hasattr(sys, 'frozen'):
import libs
base_path = os.path.dirname(os.path.dirname(os.path.abspath(libs.__file__)))
else:
base_path = os.path.dirname(os.path.abspath(__file__))
lib_dir = os.path.join(base_path, 'libs')
sys.path.insert(0, base_path)
sys.path.insert(0, lib_dir)
from couchpotato.environment import Env
class TaskBarIcon(wx.TaskBarIcon):
TBMENU_OPEN = wx.NewId()
TBMENU_SETTINGS = wx.NewId()
TBMENU_EXIT = wx.ID_EXIT
closed = False
menu = False
enabled = False
def __init__(self, frame):
wx.TaskBarIcon.__init__(self)
self.frame = frame
icon = wx.Icon('icon.png', wx.BITMAP_TYPE_PNG)
self.SetIcon(icon)
self.Bind(wx.EVT_TASKBAR_LEFT_UP, self.OnTaskBarClick)
self.Bind(wx.EVT_TASKBAR_RIGHT_UP, self.OnTaskBarClick)
self.Bind(wx.EVT_MENU, self.onOpen, id = self.TBMENU_OPEN)
self.Bind(wx.EVT_MENU, self.onSettings, id = self.TBMENU_SETTINGS)
self.Bind(wx.EVT_MENU, self.onTaskBarClose, id = self.TBMENU_EXIT)
def OnTaskBarClick(self, evt):
menu = self.CreatePopupMenu()
self.PopupMenu(menu)
menu.Destroy()
def enable(self):
self.enabled = True
if self.menu:
self.open_menu.Enable(True)
self.setting_menu.Enable(True)
self.open_menu.SetText('Open')
def CreatePopupMenu(self):
if not self.menu:
self.menu = wx.Menu()
self.open_menu = self.menu.Append(self.TBMENU_OPEN, 'Open')
self.setting_menu = self.menu.Append(self.TBMENU_SETTINGS, 'About')
self.exit_menu = self.menu.Append(self.TBMENU_EXIT, 'Quit')
if not self.enabled:
self.open_menu.Enable(False)
self.setting_menu.Enable(False)
self.open_menu.SetText('Loading...')
return self.menu
def onOpen(self, event):
url = self.frame.parent.getSetting('base_url')
webbrowser.open(url)
def onSettings(self, event):
url = self.frame.parent.getSetting('base_url') + '/settings/'
webbrowser.open(url)
def onTaskBarClose(self, evt):
if self.closed:
return
self.closed = True
self.RemoveIcon()
wx.CallAfter(self.frame.Close)
def makeIcon(self, img):
if "wxMSW" in wx.PlatformInfo:
img = img.Scale(16, 16)
elif "wxGTK" in wx.PlatformInfo:
img = img.Scale(22, 22)
icon = wx.IconFromBitmap(img.CopyFromBitmap())
return icon
class MainFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, None, style = wx.FRAME_NO_TASKBAR)
self.parent = parent
self.tbicon = TaskBarIcon(self)
class WorkerThread(Thread):
def __init__(self, desktop):
Thread.__init__(self)
self.daemon = True
self._desktop = desktop
self.start()
def run(self):
# Get options via arg
from couchpotato.runner import getOptions
args = ['--quiet']
self.options = getOptions(base_path, args)
# Load settings
settings = Env.get('settings')
settings.setFile(self.options.config_file)
# Create data dir if needed
self.data_dir = os.path.expanduser(Env.setting('data_dir'))
if self.data_dir == '':
from couchpotato.core.helpers.variable import getDataDir
self.data_dir = getDataDir()
if not os.path.isdir(self.data_dir):
os.makedirs(self.data_dir)
# Create logging dir
self.log_dir = os.path.join(self.data_dir, 'logs');
if not os.path.isdir(self.log_dir):
os.mkdir(self.log_dir)
try:
from couchpotato.runner import runCouchPotato
runCouchPotato(self.options, base_path, args, data_dir = self.data_dir, log_dir = self.log_dir, Env = Env, desktop = self._desktop)
except:
pass
self._desktop.frame.Close()
class CouchPotatoApp(wx.App, SoftwareUpdate):
settings = {}
events = {}
restart = False
closing = False
def OnInit(self):
# Updater
base_url = 'http://couchpota.to/updates/%s/' % VERSION
self.InitUpdates(base_url, base_url + 'changelog.html',
icon = wx.Icon('icon.png'))
self.frame = MainFrame(self)
self.frame.Bind(wx.EVT_CLOSE, self.onClose)
# CouchPotato thread
self.worker = WorkerThread(self)
return True
def onAppLoad(self):
self.frame.tbicon.enable()
def setSettings(self, settings = {}):
self.settings = settings
def getSetting(self, name):
return self.settings.get(name)
def addEvents(self, events = {}):
for name in events.iterkeys():
self.events[name] = events[name]
def onClose(self, event):
if not self.closing:
self.closing = True
self.frame.tbicon.onTaskBarClose(event)
onClose = self.events.get('onClose')
onClose(event)
def afterShutdown(self, restart = False):
self.frame.Destroy()
self.restart = restart
self.ExitMainLoop()
if __name__ == '__main__':
app = CouchPotatoApp(redirect = False)
app.MainLoop()
time.sleep(1)
if app.restart:
def appexe_from_executable(exepath):
appdir = appdir_from_executable(exepath)
exename = os.path.basename(exepath)
if sys.platform == "darwin":
if os.path.isdir(os.path.join(appdir, "Contents", "MacOS")):
return os.path.join(appdir, "Contents", "MacOS", exename)
return os.path.join(appdir, exename)
exe = appexe_from_executable(sys.executable)
os.chdir(os.path.dirname(exe))
os.execv(exe, [exe] + sys.argv[1:])
+3 -4
View File
@@ -5,10 +5,9 @@
* Search through the existing (and closed) issues first. See if you can get your answer there.
* Double check the result manually, because it could be an external issue.
* Post logs! Without seeing what is going on, I can't reproduce the error.
* What is the movie + quality you are searching for.
* What are you settings for the specific problem.
* What providers are you using. (While your logs include these, scanning through hundred of lines of log isn't my hobby).
* Give me a short step by step of how to reproduce.
* What are you settings for the specific problem
* What providers are you using. (While your logs include these, scanning through hundred of lines of log isn't my hobby)
* Give me a short step by step of how to reproduce
* What hardware / OS are you using and what are the limits? NAS can be slow and maybe have a different python installed then when you use CP on OSX or Windows for example.
* I will mark issues with the "can't reproduce" tag. Don't go asking me "why closed" if it clearly says the issue in the tag ;)
+1 -2
View File
@@ -78,7 +78,6 @@ def page_not_found(error):
r = '%s%s' % (request.url.rstrip('/'), index_url + '#' + url)
return redirect(r)
else:
if not Env.get('dev'):
time.sleep(0.1)
time.sleep(0.1)
return 'Wrong API key used', 404
+8 -10
View File
@@ -23,22 +23,20 @@ config = [{
'default': '',
'type': 'password',
},
{
'name': 'host',
'advanced': True,
'default': '0.0.0.0',
'hidden': True,
'label': 'IP',
'description': 'Host that I should listen to. "0.0.0.0" listens to all ips.',
},
{
'name': 'port',
'default': 5050,
'type': 'int',
'description': 'The port I should listen to.',
},
{
'name': 'ssl_cert',
'description': 'Path to SSL server.crt',
'advanced': True,
},
{
'name': 'ssl_key',
'description': 'Path to SSL server.key',
'advanced': True,
},
{
'name': 'launch_browser',
'default': True,
+2 -4
View File
@@ -152,7 +152,7 @@ class Core(Plugin):
def createBaseUrl(self):
host = Env.setting('host')
if host == '0.0.0.0' or host == '':
if host == '0.0.0.0':
host = 'localhost'
port = Env.setting('port')
@@ -176,10 +176,8 @@ class Core(Plugin):
})
def signalHandler(self):
if Env.get('daemonized'): return
def signal_handler(signal, frame):
fireEvent('app.shutdown', single = True)
fireEvent('app.do_shutdown')
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
+12 -125
View File
@@ -1,60 +1,15 @@
from couchpotato.core.event import addEvent
from couchpotato.core.helpers.variable import tryInt
from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin
from couchpotato.environment import Env
from minify.cssmin import cssmin
from minify.jsmin import jsmin
import os
import traceback
log = CPLog(__name__)
class ClientScript(Plugin):
core_static = {
'style': [
'style/main.css',
'style/uniform.generic.css',
'style/uniform.css',
'style/settings.css',
],
'script': [
'scripts/library/mootools.js',
'scripts/library/mootools_more.js',
'scripts/library/prefix_free.js',
'scripts/library/uniform.js',
'scripts/library/form_replacement/form_check.js',
'scripts/library/form_replacement/form_radio.js',
'scripts/library/form_replacement/form_dropdown.js',
'scripts/library/form_replacement/form_selectoption.js',
'scripts/library/question.js',
'scripts/library/scrollspy.js',
'scripts/library/spin.js',
'scripts/couchpotato.js',
'scripts/api.js',
'scripts/library/history.js',
'scripts/page.js',
'scripts/block.js',
'scripts/block/navigation.js',
'scripts/block/footer.js',
'scripts/block/menu.js',
'scripts/page/home.js',
'scripts/page/wanted.js',
'scripts/page/settings.js',
'scripts/page/about.js',
'scripts/page/manage.js',
],
}
urls = {'style': {}, 'script': {}, }
minified = {'style': {}, 'script': {}, }
paths = {'style': {}, 'script': {}, }
comment = {
'style': '/*** %s:%d ***/\n',
'script': '// %s:%d\n'
urls = {
'style': {},
'script': {},
}
html = {
@@ -69,66 +24,6 @@ class ClientScript(Plugin):
addEvent('clientscript.get_styles', self.getStyles)
addEvent('clientscript.get_scripts', self.getScripts)
addEvent('app.load', self.minify)
self.addCore()
def addCore(self):
for static_type in self.core_static:
for rel_path in self.core_static.get(static_type):
file_path = os.path.join(Env.get('app_dir'), 'couchpotato', 'static', rel_path)
core_url = 'api/%s/static/%s?%s' % (Env.setting('api_key'), rel_path, tryInt(os.path.getmtime(file_path)))
if static_type == 'script':
self.registerScript(core_url, file_path, position = 'front')
else:
self.registerStyle(core_url, file_path, position = 'front')
def minify(self):
for file_type in ['style', 'script']:
ext = 'js' if file_type is 'script' else 'css'
positions = self.paths.get(file_type, {})
for position in positions:
files = positions.get(position)
self._minify(file_type, files, position, position + '.' + ext)
def _minify(self, file_type, files, position, out):
cache = Env.get('cache_dir')
out_name = 'minified_' + out
out = os.path.join(cache, out_name)
raw = []
for file_path in files:
f = open(file_path, 'r').read()
if file_type == 'script':
data = jsmin(f)
else:
data = cssmin(f)
data = data.replace('../images/', '../static/images/')
raw.append({'file': file_path, 'date': int(os.path.getmtime(file_path)), 'data': data})
# Combine all files together with some comments
data = ''
for r in raw:
data += self.comment.get(file_type) % (r.get('file'), r.get('date'))
data += r.get('data') + '\n\n'
self.createFile(out, data.strip())
if not self.minified.get(file_type):
self.minified[file_type] = {}
if not self.minified[file_type].get(position):
self.minified[file_type][position] = []
minified_url = 'api/%s/file.cache/%s?%s' % (Env.setting('api_key'), out_name, tryInt(os.path.getmtime(out)))
self.minified[file_type][position].append(minified_url)
def getStyles(self, *args, **kwargs):
return self.get('style', *args, **kwargs)
@@ -140,30 +35,22 @@ class ClientScript(Plugin):
data = '' if as_html else []
try:
try:
if not Env.get('dev'):
return self.minified[type][location]
except:
pass
return self.urls[type][location]
except:
log.error('Error getting minified %s, %s: %s', (type, location, traceback.format_exc()))
except Exception, e:
log.error(e)
return data
def registerStyle(self, api_path, file_path, position = 'head'):
self.register(api_path, file_path, 'style', position)
def registerStyle(self, path, position = 'head'):
self.register(path, 'style', position)
def registerScript(self, api_path, file_path, position = 'head'):
self.register(api_path, file_path, 'script', position)
def registerScript(self, path, position = 'head'):
self.register(path, 'script', position)
def register(self, api_path, file_path, type, location):
def register(self, filepath, type, location):
if not self.urls[type].get(location):
self.urls[type][location] = []
self.urls[type][location].append(api_path)
if not self.paths[type].get(location):
self.paths[type][location] = []
self.paths[type][location].append(file_path)
filePath = filepath
self.urls[type][location].append(filePath)
+1
View File
@@ -2,6 +2,7 @@ from apscheduler.scheduler import Scheduler as Sched
from couchpotato.core.event import addEvent
from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin
import logging
log = CPLog(__name__)
-13
View File
@@ -1,13 +0,0 @@
config = {
'name': 'download_providers',
'groups': [
{
'label': 'Downloaders',
'description': 'You can select different downloaders for each type (usenet / torrent)',
'type': 'list',
'name': 'download_providers',
'tab': 'downloaders',
'options': [],
},
],
}
+11 -44
View File
@@ -33,44 +33,18 @@ class Downloader(Provider):
]
def __init__(self):
addEvent('download', self._download)
addEvent('download.enabled', self._isEnabled)
addEvent('download.enabled_types', self.getEnabledDownloadType)
addEvent('download.status', self._getAllDownloadStatus)
addEvent('download.remove_failed', self._removeFailed)
addEvent('download', self.download)
addEvent('download.status', self.getAllDownloadStatus)
addEvent('download.remove_failed', self.removeFailed)
def getEnabledDownloadType(self):
for download_type in self.type:
if self.isEnabled(manual = True, data = {'type': download_type}):
return self.type
return []
def _download(self, data = {}, movie = {}, manual = False, filedata = None):
if self.isDisabled(manual, data):
return
return self.download(data = data, movie = movie, filedata = filedata)
def _getAllDownloadStatus(self):
if self.isDisabled(manual = True, data = {}):
return
return self.getAllDownloadStatus()
def download(self, data = {}, movie = {}, manual = False, filedata = None):
pass
def getAllDownloadStatus(self):
return
def _removeFailed(self, item):
if self.isDisabled(manual = True, data = {}):
return
if self.conf('delete_failed', default = True):
return self.removeFailed(item)
return False
def removeFailed(self, item):
return
def removeFailed(self, name = {}, nzo_id = {}):
return False
def isCorrectType(self, item_type):
is_correct = item_type in self.type
@@ -103,16 +77,9 @@ class Downloader(Provider):
log.error('Failed converting magnet url to torrent: %s', (torrent_hash))
return False
def isDisabled(self, manual, data):
return not self.isEnabled(manual, data)
def isDisabled(self, manual):
return not self.isEnabled(manual)
def _isEnabled(self, manual, data = {}):
if not self.isEnabled(manual, data):
return
return True
def isEnabled(self, manual, data = {}):
def isEnabled(self, manual):
d_manual = self.conf('manual', default = False)
return super(Downloader, self).isEnabled() and \
((d_manual and manual) or (d_manual is False)) and \
(not data or self.isCorrectType(data.get('type')))
return super(Downloader, self).isEnabled() and ((d_manual and manual) or (d_manual is False))
@@ -10,7 +10,6 @@ config = [{
'groups': [
{
'tab': 'downloaders',
'list': 'download_providers',
'name': 'blackhole',
'label': 'Black hole',
'description': 'Download the NZB/Torrent to a specific folder.',
+5 -22
View File
@@ -1,7 +1,6 @@
from __future__ import with_statement
from couchpotato.core.downloaders.base import Downloader
from couchpotato.core.logger import CPLog
from couchpotato.environment import Env
import os
import traceback
@@ -11,7 +10,11 @@ class Blackhole(Downloader):
type = ['nzb', 'torrent', 'torrent_magnet']
def download(self, data = {}, movie = {}, filedata = None):
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', 'torrent' if 'torrent' in data.get('type') else data.get('type')])):
return
directory = self.conf('directory')
if not directory or not os.path.isdir(directory):
@@ -37,7 +40,6 @@ class Blackhole(Downloader):
log.info('Downloading %s to %s.', (data.get('type'), fullPath))
with open(fullPath, 'wb') as f:
f.write(filedata)
os.chmod(fullPath, Env.getPermission('file'))
return True
else:
log.info('File %s already exists.', fullPath)
@@ -50,23 +52,4 @@ class Blackhole(Downloader):
except:
log.info('Failed to download file %s: %s', (data.get('name'), traceback.format_exc()))
return False
return False
def getEnabledDownloadType(self):
if self.conf('use_for') == 'both':
return super(Blackhole, self).getEnabledDownloadType()
elif self.conf('use_for') == 'torrent':
return ['torrent', 'torrent_magnet']
else:
return ['nzb']
def isEnabled(self, manual, data = {}):
for_type = ['both']
if data and 'torrent' in data.get('type'):
for_type.append('torrent')
elif data:
for_type.append(data.get('type'))
return super(Blackhole, self).isEnabled(manual, data) and \
((self.conf('use_for') in for_type))
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'downloaders',
'list': 'download_providers',
'name': 'nzbget',
'label': 'NZBGet',
'description': 'Use <a href="http://nzbget.sourceforge.net/Main_Page" target="_blank">NZBGet</a> to download NZBs.',
@@ -34,13 +33,6 @@ config = [{
'default': 'Movies',
'description': 'The category CP places the nzb in. Like <strong>movies</strong> or <strong>couchpotato</strong>',
},
{
'name': 'priority',
'default': '0',
'type': 'dropdown',
'values': [('Very Low', -100), ('Low', -50), ('Normal', 0), ('High', 50), ('Very High', 100)],
'description': 'Only change this if you are using NZBget 9.0 or higher',
},
{
'name': 'manual',
'default': 0,
+7 -11
View File
@@ -1,9 +1,7 @@
from base64 import standard_b64encode
from couchpotato.core.downloaders.base import Downloader
from couchpotato.core.helpers.encoding import ss
from couchpotato.core.helpers.variable import tryInt
from couchpotato.core.logger import CPLog
import re
from inspect import isfunction
import socket
import traceback
import xmlrpclib
@@ -16,7 +14,10 @@ class NZBGet(Downloader):
url = 'http://nzbget:%(password)s@%(host)s/xmlrpc'
def download(self, data = {}, movie = {}, filedata = None):
def download(self, data = {}, movie = {}, manual = False, filedata = None):
if self.isDisabled(manual) or not self.isCorrectType(data.get('type')):
return
if not filedata:
log.error('Unable to get NZB file: %s', traceback.format_exc())
@@ -25,7 +26,7 @@ class NZBGet(Downloader):
log.info('Sending "%s" to NZBGet.', data.get('name'))
url = self.url % {'host': self.conf('host'), 'password': self.conf('password')}
nzb_name = ss('%s.nzb' % self.createNzbName(data, movie))
nzb_name = '%s.nzb' % self.createNzbName(data, movie)
rpc = xmlrpclib.ServerProxy(url)
try:
@@ -43,12 +44,7 @@ class NZBGet(Downloader):
log.error('Protocol Error: %s', e)
return False
if re.search(r"^0", rpc.version()):
xml_response = rpc.append(nzb_name, self.conf('category'), False, standard_b64encode(filedata.strip()))
else:
xml_response = rpc.append(nzb_name, self.conf('category'), tryInt(self.conf('priority')), False, standard_b64encode(filedata.strip()))
if xml_response:
if rpc.append(nzb_name, self.conf('category'), False, standard_b64encode(filedata.strip())):
log.info('NZB sent successfully to NZBGet')
return True
else:
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'downloaders',
'list': 'download_providers',
'name': 'nzbvortex',
'label': 'NZBVortex',
'description': 'Use <a href="http://www.nzbvortex.com/landing/" target="_blank">NZBVortex</a> to download NZBs.',
+10 -4
View File
@@ -22,7 +22,10 @@ class NZBVortex(Downloader):
api_level = None
session_id = None
def download(self, data = {}, movie = {}, filedata = None):
def download(self, data = {}, movie = {}, manual = False, filedata = None):
if self.isDisabled(manual) or not self.isCorrectType(data.get('type')) or not self.getApiLevel():
return
# Send the nzb
try:
@@ -36,6 +39,9 @@ class NZBVortex(Downloader):
def getAllDownloadStatus(self):
if self.isDisabled(manual = True):
return False
raw_statuses = self.call('nzb')
statuses = []
@@ -60,6 +66,9 @@ class NZBVortex(Downloader):
def removeFailed(self, item):
if not self.conf('delete_failed', default = True):
return False
log.info('%s failed downloading, deleting...', item['name'])
try:
@@ -144,9 +153,6 @@ class NZBVortex(Downloader):
return self.api_level
def isEnabled(self, manual, data):
return super(NZBVortex, self).isEnabled(manual, data) and self.getApiLevel()
class HTTPSConnection(httplib.HTTPSConnection):
def __init__(self, *args, **kwargs):
@@ -9,7 +9,6 @@ config = [{
'groups': [
{
'tab': 'downloaders',
'list': 'download_providers',
'name': 'pneumatic',
'label': 'Pneumatic',
'description': 'Use <a href="http://forum.xbmc.org/showthread.php?tid=97657" target="_blank">Pneumatic</a> to download .strm files.',
@@ -11,7 +11,9 @@ class Pneumatic(Downloader):
type = ['nzb']
strm_syntax = 'plugin://plugin.program.pneumatic/?mode=strm&type=add_file&nzb=%s&nzbname=%s'
def download(self, data = {}, movie = {}, filedata = None):
def download(self, data = {}, movie = {}, manual = False, filedata = None):
if self.isDisabled(manual) or (not self.isCorrectType(data.get('type'))):
return
directory = self.conf('directory')
if not directory or not os.path.isdir(directory):
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'downloaders',
'list': 'download_providers',
'name': 'sabnzbd',
'label': 'Sabnzbd',
'description': 'Use <a href="http://sabnzbd.org/" target="_blank">SABnzbd</a> to download NZBs.',
+12 -5
View File
@@ -2,7 +2,6 @@ from couchpotato.core.downloaders.base import Downloader
from couchpotato.core.helpers.encoding import tryUrlencode, ss
from couchpotato.core.helpers.variable import cleanHost, mergeDicts
from couchpotato.core.logger import CPLog
from couchpotato.environment import Env
from urllib2 import URLError
import json
import traceback
@@ -13,7 +12,10 @@ class Sabnzbd(Downloader):
type = ['nzb']
def download(self, data = {}, movie = {}, filedata = None):
def download(self, data = {}, movie = {}, manual = False, filedata = None):
if self.isDisabled(manual) or not self.isCorrectType(data.get('type')):
return
log.info('Sending "%s" to SABnzbd.', data.get('name'))
@@ -39,9 +41,9 @@ class Sabnzbd(Downloader):
try:
if params.get('mode') is 'addfile':
sab = self.urlopen(url, timeout = 60, params = {'nzbfile': (ss(nzb_filename), filedata)}, multipart = True, show_error = False, headers = {'User-Agent': Env.getIdentifier()})
sab = self.urlopen(url, timeout = 60, params = {'nzbfile': (ss(nzb_filename), filedata)}, multipart = True, show_error = False)
else:
sab = self.urlopen(url, timeout = 60, show_error = False, headers = {'User-Agent': Env.getIdentifier()})
sab = self.urlopen(url, timeout = 60, show_error = False)
except URLError:
log.error('Failed sending release, probably wrong HOST: %s', traceback.format_exc(0))
return False
@@ -63,6 +65,8 @@ class Sabnzbd(Downloader):
return False
def getAllDownloadStatus(self):
if self.isDisabled(manual = True):
return False
log.debug('Checking SABnzbd download status.')
@@ -118,6 +122,9 @@ class Sabnzbd(Downloader):
def removeFailed(self, item):
if not self.conf('delete_failed', default = True):
return False
log.info('%s failed downloading, deleting...', item['name'])
try:
@@ -140,7 +147,7 @@ class Sabnzbd(Downloader):
'output': 'json'
}))
data = self.urlopen(url, timeout = 60, show_error = False, headers = {'User-Agent': Env.getIdentifier()})
data = self.urlopen(url, timeout = 60, show_error = False)
if use_json:
d = json.loads(data)
if d.get('error'):
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'downloaders',
'list': 'download_providers',
'name': 'synology',
'label': 'Synology',
'description': 'Use <a href="http://www.synology.com/dsm/home_home_applications_download_station.php" target="_blank">Synology Download Station</a> to download.',
@@ -14,7 +14,10 @@ class Synology(Downloader):
type = ['torrent_magnet']
log = CPLog(__name__)
def download(self, data, movie, filedata = None):
def download(self, data, movie, manual = False, filedata = None):
if self.isDisabled(manual) or not self.isCorrectType(data.get('type')):
return
log.error('Sending "%s" (%s) to Synology.', (data.get('name'), data.get('type')))
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'downloaders',
'list': 'download_providers',
'name': 'transmission',
'label': 'Transmission',
'description': 'Use <a href="http://www.transmissionbt.com/" target="_blank">Transmission</a> to download torrents.',
@@ -16,7 +16,10 @@ class Transmission(Downloader):
type = ['torrent', 'torrent_magnet']
log = CPLog(__name__)
def download(self, data, movie, filedata = None):
def download(self, data, movie, manual = False, filedata = None):
if self.isDisabled(manual) or not self.isCorrectType(data.get('type')):
return
log.debug('Sending "%s" (%s) to Transmission.', (data.get('name'), data.get('type')))
@@ -38,12 +41,10 @@ class Transmission(Downloader):
'download-dir': folder_path
}
torrent_params = {}
if self.conf('ratio'):
torrent_params = {
'seedRatioLimit': self.conf('ratio'),
'seedRatioMode': self.conf('ratio')
}
torrent_params = {
'seedRatioLimit': self.conf('ratio'),
'seedRatioMode': (0 if self.conf('ratio') else 1)
}
if not filedata and data.get('type') == 'torrent':
log.error('Failed sending torrent, no data')
@@ -59,8 +60,7 @@ class Transmission(Downloader):
remote_torrent = trpc.add_torrent_file(b64encode(filedata), arguments = params)
# Change settings of added torrents
if torrent_params:
trpc.set_torrent(remote_torrent['torrent-added']['hashString'], torrent_params)
trpc.set_torrent(remote_torrent['torrent-added']['hashString'], torrent_params)
return True
except Exception, err:
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'downloaders',
'list': 'download_providers',
'name': 'utorrent',
'label': 'uTorrent',
'description': 'Use <a href="http://www.utorrent.com/" target="_blank">uTorrent</a> to download torrents.',
+8 -67
View File
@@ -1,13 +1,11 @@
from base64 import b16encode, b32decode
from bencode import bencode, bdecode
from couchpotato.core.downloaders.base import Downloader
from couchpotato.core.helpers.encoding import isInt, ss
from couchpotato.core.helpers.encoding import isInt
from couchpotato.core.logger import CPLog
from hashlib import sha1
from multipartpost import MultipartPostHandler
import cookielib
import httplib
import json
import re
import time
import urllib
@@ -22,7 +20,10 @@ class uTorrent(Downloader):
type = ['torrent', 'torrent_magnet']
utorrent_api = None
def download(self, data, movie, filedata = None):
def download(self, data, movie, manual = False, filedata = None):
if self.isDisabled(manual) or not self.isCorrectType(data.get('type')):
return
log.debug('Sending "%s" (%s) to uTorrent.', (data.get('name'), data.get('type')))
@@ -39,7 +40,6 @@ class uTorrent(Downloader):
if not filedata and data.get('type') == 'torrent':
log.error('Failed sending torrent, no data')
return False
if data.get('type') == 'torrent_magnet':
torrent_hash = re.findall('urn:btih:([\w]{32,40})', data.get('url'))[0].upper()
torrent_params['trackers'] = '%0D%0A%0D%0A'.join(self.torrent_trackers)
@@ -48,10 +48,6 @@ class uTorrent(Downloader):
torrent_hash = sha1(bencode(info)).hexdigest().upper()
torrent_filename = self.createFileName(data, filedata, movie)
# Convert base 32 to hex
if len(torrent_hash) == 32:
torrent_hash = b16encode(b32decode(torrent_hash))
# Send request to uTorrent
try:
if not self.utorrent_api:
@@ -71,59 +67,6 @@ class uTorrent(Downloader):
log.error('Failed to send torrent to uTorrent: %s', err)
return False
def getAllDownloadStatus(self):
log.debug('Checking uTorrent download status.')
# Load host from config and split out port.
host = self.conf('host').split(':')
if not isInt(host[1]):
log.error('Config properties are not filled in correctly, port is missing.')
return False
try:
self.utorrent_api = uTorrentAPI(host[0], port = host[1], username = self.conf('username'), password = self.conf('password'))
except Exception, err:
log.error('Failed to get uTorrent object: %s', err)
return False
data = ''
try:
data = self.utorrent_api.get_status()
queue = json.loads(data)
if queue.get('error'):
log.error('Error getting data from uTorrent: %s', queue.get('error'))
return False
except Exception, err:
log.error('Failed to get status from uTorrent: %s', err)
return False
if queue.get('torrents', []) == []:
log.debug('Nothing in queue')
return False
statuses = []
# Get torrents
for item in queue.get('torrents', []):
# item[21] = Paused | Downloading | Seeding | Finished
status = 'busy'
if item[21] == 'Finished' or item[21] == 'Seeding':
status = 'completed'
statuses.append({
'id': item[0],
'name': item[2],
'status': status,
'original_status': item[1],
'timeleft': item[10],
})
return statuses
class uTorrentAPI(object):
@@ -154,7 +97,9 @@ class uTorrentAPI(object):
try:
open_request = self.opener.open(request)
response = open_request.read()
log.debug('response: %s', response)
if response:
log.debug('uTorrent action successfull')
return response
else:
log.debug('Unknown failure sending command to uTorrent. Return text is: %s', response)
@@ -180,7 +125,7 @@ class uTorrentAPI(object):
def add_torrent_file(self, filename, filedata):
action = "action=add-file"
return self._request(action, {"torrent_file": (ss(filename), filedata)})
return self._request(action, {"torrent_file": (filename, filedata)})
def set_torrent(self, hash, params):
action = "action=setprops&hash=%s" % hash
@@ -191,7 +136,3 @@ class uTorrentAPI(object):
def pause_torrent(self, hash):
action = "action=pause&hash=%s" % hash
return self._request(action)
def get_status(self):
action = "list=1"
return self._request(action)
+1 -4
View File
@@ -104,8 +104,6 @@ def fireEvent(name, *args, **kwargs):
# Merge
if options['merge'] and len(results) > 0:
results.reverse() # Priority 1 is higher then 100
# Dict
if isinstance(results[0], dict):
merged = {}
@@ -117,8 +115,7 @@ def fireEvent(name, *args, **kwargs):
elif isinstance(results[0], list):
merged = []
for result in results:
if result not in merged:
merged += result
merged += result
results = merged
+1 -1
View File
@@ -168,4 +168,4 @@ def randomString(size = 8, chars = string.ascii_uppercase + string.digits):
return ''.join(random.choice(chars) for x in range(size))
def splitString(str, split_on = ','):
return [x.strip() for x in str.split(split_on)] if str else []
return [x.strip() for x in str.split(split_on)]
-12
View File
@@ -67,18 +67,6 @@ class Loader(object):
def addFromDir(self, plugin_type, priority, module, dir_name):
# Load dir module
try:
m = __import__(module)
splitted = module.split('.')
for sub in splitted[1:]:
m = getattr(m, sub)
if hasattr(m, 'config'):
fireEvent('settings.options', splitted[-1] + '_config', getattr(m, 'config'))
except:
raise
for cur_file in glob.glob(os.path.join(dir_name, '*')):
name = os.path.basename(cur_file)
if os.path.isdir(os.path.join(dir_name, name)):
@@ -1,25 +0,0 @@
from migrate.changeset.schema import create_column
from sqlalchemy.schema import MetaData, Column, Table, Index
from sqlalchemy.types import Integer
meta = MetaData()
def upgrade(migrate_engine):
meta.bind = migrate_engine
# Change release, add last_edit and index
last_edit_column = Column('last_edit', Integer)
release = Table('release', meta, last_edit_column)
create_column(last_edit_column, release)
Index('ix_release_last_edit', release.c.last_edit).create()
# Change movie last_edit
last_edit_column = Column('last_edit', Integer)
movie = Table('movie', meta, last_edit_column)
Index('ix_movie_last_edit', movie.c.last_edit).create()
def downgrade(migrate_engine):
pass
@@ -1,13 +0,0 @@
config = {
'name': 'notification_providers',
'groups': [
{
'label': 'Notifications',
'description': 'Notify when movies are done or snatched',
'type': 'list',
'name': 'notification_providers',
'tab': 'notifications',
'options': [],
},
],
}
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'notifications',
'list': 'notification_providers',
'name': 'boxcar',
'options': [
{
@@ -178,14 +178,11 @@ var NotificationBase = new Class({
},
addTestButton: function(fieldset, plugin_name){
var self = this,
button_name = self.testButtonName(fieldset);
if(button_name.contains('Notifications')) return;
var self = this;
new Element('.ctrlHolder.test_button').adopt(
new Element('a.button', {
'text': button_name,
'text': self.testButtonName(fieldset),
'events': {
'click': function(){
var button = fieldset.getElement('.test_button .button');
@@ -194,7 +191,7 @@ var NotificationBase = new Class({
Api.request('notify.'+plugin_name+'.test', {
'onComplete': function(json){
button.set('text', button_name);
button.set('text', self.testButtonName(fieldset));
if(json.success){
var message = new Element('span.success', {
@@ -1,56 +0,0 @@
from .main import Email
def start():
return Email()
config = [{
'name': 'email',
'groups': [
{
'tab': 'notifications',
'list': 'notification_providers',
'name': 'email',
'options': [
{
'name': 'enabled',
'default': 0,
'type': 'enabler',
},
{
'name': 'from',
'label': 'Send e-mail from',
},
{
'name': 'to',
'label': 'Send e-mail to',
},
{
'name': 'smtp_server',
'label': 'SMTP server',
},
{
'name': 'ssl',
'label': 'Enable SSL',
'default': 0,
'type': 'bool',
},
{
'name': 'smtp_user',
'label': 'SMTP user',
},
{
'name': 'smtp_pass',
'label': 'SMTP password',
'type': 'password',
},
{
'name': 'on_snatch',
'default': 0,
'type': 'bool',
'advanced': True,
'description': 'Also send message when movie is snatched.',
},
],
}
],
}]
@@ -1,55 +0,0 @@
from couchpotato.core.helpers.encoding import toUnicode
from couchpotato.core.helpers.variable import splitString
from couchpotato.core.logger import CPLog
from couchpotato.core.notifications.base import Notification
from email.mime.text import MIMEText
import smtplib
import traceback
log = CPLog(__name__)
class Email(Notification):
def notify(self, message = '', data = {}, listener = None):
if self.isDisabled(): return
# Extract all the settings from settings
from_address = self.conf('from')
to_address = self.conf('to')
ssl = self.conf('ssl')
smtp_server = self.conf('smtp_server')
smtp_user = self.conf('smtp_user')
smtp_pass = self.conf('smtp_pass')
# Make the basic message
message = MIMEText(toUnicode(message))
message['Subject'] = self.default_title
message['From'] = from_address
message['To'] = to_address
try:
# Open the SMTP connection, via SSL if requested
log.debug("SMTP over SSL %s", ("enabled" if ssl == 1 else "disabled"))
mailserver = smtplib.SMTP_SSL(smtp_server) if ssl == 1 else smtplib.SMTP(smtp_server)
# Check too see if an login attempt should be attempted
if len(smtp_user) > 0:
log.debug("Logging on to SMTP server using username \'%s\'%s", (smtp_user, " and a password" if len(smtp_pass) > 0 else ""))
mailserver.login(smtp_user, smtp_pass)
# Send the e-mail
log.debug("Sending the email")
mailserver.sendmail(from_address, splitString(to_address), message.as_string())
# Close the SMTP connection
mailserver.quit()
log.info('Email notification sent')
return True
except:
log.error('E-mail failed: %s', traceback.format_exc())
return False
return False
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'notifications',
'list': 'notification_providers',
'name': 'growl',
'description': 'Version 1.4+',
'options': [
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'notifications',
'list': 'notification_providers',
'name': 'nmj',
'label': 'NMJ',
'options': [
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'notifications',
'list': 'notification_providers',
'name': 'notifo',
'description': 'Keep in mind that Notifo service will end soon.',
'options': [
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'notifications',
'list': 'notification_providers',
'name': 'notifymyandroid',
'label': 'Notify My Android',
'options': [
@@ -8,9 +8,8 @@ config = [{
'groups': [
{
'tab': 'notifications',
'list': 'notification_providers',
'name': 'notifymywp',
'label': 'Windows Phone',
'label': 'Notify My Windows Phone',
'options': [
{
'name': 'enabled',
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'notifications',
'list': 'notification_providers',
'name': 'plex',
'options': [
{
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'notifications',
'list': 'notification_providers',
'name': 'prowl',
'options': [
{
@@ -1,48 +0,0 @@
from .main import Pushalot
def start():
return Pushalot()
config = [{
'name': 'pushalot',
'groups': [
{
'tab': 'notifications',
'list': 'notification_providers',
'name': 'pushalot',
'description': 'for Windows Phone and Windows 8',
'options': [
{
'name': 'enabled',
'default': 0,
'type': 'enabler',
},
{
'name': 'auth_token',
'label': 'Auth Token',
},
{
'name': 'silent',
'label': 'Silent',
'default': 0,
'type': 'bool',
'description': 'Don\'t send Toast notifications. Only update Live Tile',
},
{
'name': 'important',
'label': 'High Priority',
'default': 0,
'type': 'bool',
'description': 'Send message with High priority.',
},
{
'name': 'on_snatch',
'default': 0,
'type': 'bool',
'advanced': True,
'description': 'Also send message when movie is snatched.',
},
],
}
],
}]
@@ -1,37 +0,0 @@
from couchpotato.core.helpers.encoding import toUnicode
from couchpotato.core.logger import CPLog
from couchpotato.core.notifications.base import Notification
import traceback
log = CPLog(__name__)
class Pushalot(Notification):
urls = {
'api': 'https://pushalot.com/api/sendmessage'
}
def notify(self, message = '', data = {}, listener = None):
if self.isDisabled(): return
data = {
'AuthorizationToken': self.conf('auth_token'),
'Title': self.default_title,
'Body': toUnicode(message),
'LinkTitle': toUnicode("CouchPotato"),
'link': toUnicode("https://couchpota.to/"),
'IsImportant': self.conf('important'),
'IsSilent': self.conf('silent'),
}
headers = {
'Content-type': 'application/x-www-form-urlencoded'
}
try:
self.urlopen(self.urls['api'], headers = headers, params = data, multipart = True, show_error = False)
return True
except:
log.error('PushAlot failed: %s', traceback.format_exc())
return False
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'notifications',
'list': 'notification_providers',
'name': 'pushover',
'options': [
{
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'notifications',
'list': 'notification_providers',
'name': 'synoindex',
'description': 'Automaticly adds index to Synology Media Server.',
'options': [
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'notifications',
'list': 'notification_providers',
'name': 'toasty',
'options': [
{
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'notifications',
'list': 'notification_providers',
'name': 'twitter',
'options': [
{
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'notifications',
'list': 'notification_providers',
'name': 'xbmc',
'label': 'XBMC',
'description': 'v11 (Eden) and v12 (Frodo)',
+4 -5
View File
@@ -13,7 +13,6 @@ class XBMC(Notification):
listen_to = ['renamer.after']
use_json_notifications = {}
couch_logo_url = 'https://raw.github.com/RuudBurger/CouchPotatoServer/master/couchpotato/static/images/xbmc-notify.png'
def notify(self, message = '', data = {}, listener = None):
if self.isDisabled(): return
@@ -28,7 +27,7 @@ class XBMC(Notification):
if self.use_json_notifications.get(host):
response = self.request(host, [
('GUI.ShowNotification', {'title': self.default_title, 'message': message, 'image': self.couch_logo_url}),
('GUI.ShowNotification', {'title':self.default_title, 'message':message}),
('VideoLibrary.Scan', {}),
])
else:
@@ -90,7 +89,7 @@ class XBMC(Notification):
self.use_json_notifications[host] = True
# send the text message
resp = self.request(host, [('GUI.ShowNotification', {'title':self.default_title, 'message':message, 'image':self.couch_logo_url})])
resp = self.request(host, [('GUI.ShowNotification', {'title':self.default_title, 'message':message})])
for result in resp:
if (result.get('result') and result['result'] == 'OK'):
log.debug('Message delivered successfully!')
@@ -112,8 +111,8 @@ class XBMC(Notification):
server = 'http://%s/xbmcCmds/' % host
# Notification(title, message [, timeout , image])
cmd = "xbmcHttp?command=ExecBuiltIn(Notification(%s,%s,'',%s))" % (urllib.quote(data['title']), urllib.quote(data['message']), urllib.quote(self.couch_logo_url))
# title, message [, timeout , image #can be added!]
cmd = "xbmcHttp?command=ExecBuiltIn(Notification('%s','%s'))" % (urllib.quote(data['title']), urllib.quote(data['message']))
server += cmd
# I have no idea what to set to, just tried text/plain and seems to be working :)
+1 -1
View File
@@ -12,7 +12,7 @@ class Automation(Plugin):
fireEvent('schedule.interval', 'automation.add_movies', self.addMovies, hours = self.conf('hour', default = 12))
if not Env.get('dev'):
if Env.get('dev'):
addEvent('app.load', self.addMovies)
def addMovies(self):
+3 -2
View File
@@ -64,7 +64,7 @@ class Plugin(object):
for f in glob.glob(os.path.join(self.plugin_path, 'static', '*')):
ext = getExt(f)
if ext in ['js', 'css']:
fireEvent('register_%s' % ('script' if ext in 'js' else 'style'), path + os.path.basename(f), f)
fireEvent('register_%s' % ('script' if ext in 'js' else 'style'), path + os.path.basename(f))
def showStatic(self, filename):
d = os.path.join(self.plugin_path, 'static')
@@ -78,7 +78,7 @@ class Plugin(object):
self.makeDir(os.path.dirname(path))
try:
f = open(path, 'w+' if not binary else 'w+b')
f = open(path, 'w' if not binary else 'wb')
f.write(content)
f.close()
os.chmod(path, Env.getPermission('file'))
@@ -240,6 +240,7 @@ class Plugin(object):
del kwargs['cache_timeout']
data = self.urlopen(url, **kwargs)
if data:
self.setCache(cache_key, data, timeout = cache_timeout)
return data
+2 -2
View File
@@ -15,7 +15,7 @@ if os.name == 'nt':
raise ImportError("Missing the win32file module, which is a part of the prerequisite \
pywin32 package. You can get it from http://sourceforge.net/projects/pywin32/files/pywin32/");
else:
import win32file #@UnresolvedImport
import win32file
class FileBrowser(Plugin):
@@ -98,7 +98,7 @@ class FileBrowser(Plugin):
def has_hidden_attribute(self, filepath):
try:
attrs = ctypes.windll.kernel32.GetFileAttributesW(unicode(filepath)) #@UndefinedVariable
attrs = ctypes.windll.kernel32.GetFileAttributesW(unicode(filepath))
assert attrs != -1
result = bool(attrs & 2)
except (AttributeError, AssertionError):
@@ -1,6 +0,0 @@
from .main import Dashboard
def start():
return Dashboard()
config = []
-134
View File
@@ -1,134 +0,0 @@
from couchpotato import get_session
from couchpotato.api import addApiView
from couchpotato.core.event import fireEvent
from couchpotato.core.helpers.request import jsonified, getParams
from couchpotato.core.helpers.variable import splitString, tryInt
from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin
from couchpotato.core.settings.model import Movie
from sqlalchemy.orm import joinedload_all
import random
import time
log = CPLog(__name__)
class Dashboard(Plugin):
def __init__(self):
addApiView('dashboard.suggestions', self.suggestView)
addApiView('dashboard.soon', self.getSoonView)
def newSuggestions(self):
movies = fireEvent('movie.list', status = ['active', 'done'], limit_offset = (20, 0), single = True)
movie_identifiers = [m['library']['identifier'] for m in movies[1]]
ignored_movies = fireEvent('movie.list', status = ['ignored', 'deleted'], limit_offset = (100, 0), single = True)
ignored_identifiers = [m['library']['identifier'] for m in ignored_movies[1]]
suggestions = fireEvent('movie.suggest', movies = movie_identifiers, ignore = ignored_identifiers, single = True)
suggest_status = fireEvent('status.get', 'suggest', single = True)
for suggestion in suggestions:
fireEvent('movie.add', params = {'identifier': suggestion}, force_readd = False, search_after = False, status_id = suggest_status.get('id'))
def suggestView(self):
db = get_session()
movies = db.query(Movie).limit(20).all()
identifiers = [m.library.identifier for m in movies]
suggestions = fireEvent('movie.suggest', movies = identifiers, single = True)
return jsonified({
'result': True,
'suggestions': suggestions
})
def getSoonView(self):
params = getParams()
db = get_session()
now = time.time()
# Get profiles first, determine pre or post theater
profiles = fireEvent('profile.all', single = True)
qualities = fireEvent('quality.all', single = True)
pre_releases = fireEvent('quality.pre_releases', single = True)
id_pre = {}
for quality in qualities:
id_pre[quality.get('id')] = quality.get('identifier') in pre_releases
# See what the profile contain and cache it
profile_pre = {}
for profile in profiles:
contains = {}
for profile_type in profile.get('types', []):
contains['theater' if id_pre.get(profile_type.get('quality_id')) else 'dvd'] = True
profile_pre[profile.get('id')] = contains
# Get all active movies
active_status = fireEvent('status.get', 'active', single = True)
subq = db.query(Movie).filter(Movie.status_id == active_status.get('id')).subquery()
q = db.query(Movie).join((subq, subq.c.id == Movie.id)) \
.options(joinedload_all('releases')) \
.options(joinedload_all('profile.types')) \
.options(joinedload_all('library.titles')) \
.options(joinedload_all('library.files')) \
.options(joinedload_all('status')) \
.options(joinedload_all('files'))
# Add limit
limit_offset = params.get('limit_offset')
limit = 12
if limit_offset:
splt = splitString(limit_offset) if isinstance(limit_offset, (str, unicode)) else limit_offset
limit = tryInt(splt[0])
all_movies = q.all()
if params.get('random', False):
random.shuffle(all_movies)
movies = []
for movie in all_movies:
pp = profile_pre.get(movie.profile.id)
eta = movie.library.info.get('release_date', {}) or {}
coming_soon = False
# Theater quality
if pp.get('theater') and fireEvent('searcher.could_be_released', True, eta, single = True):
coming_soon = True
if pp.get('dvd') and fireEvent('searcher.could_be_released', False, eta, single = True):
coming_soon = True
if coming_soon:
temp = movie.to_dict({
'profile': {'types': {}},
'releases': {'files':{}, 'info': {}},
'library': {'titles': {}, 'files':{}},
'files': {},
})
# Don't list older movies
if ((not params.get('late') and (not eta.get('dvd') or (eta.get('dvd') and eta.get('dvd') > (now - 2419200)))) or \
(params.get('late') and eta.get('dvd') and eta.get('dvd') < (now - 2419200))):
movies.append(temp)
if len(movies) >= limit:
break
return jsonified({
'success': True,
'empty': len(movies) == 0,
'movies': movies,
})
getLateView = getSoonView
+1 -1
View File
@@ -71,7 +71,7 @@ class FileManager(Plugin):
db = get_session()
for root, dirs, walk_files in os.walk(Env.get('cache_dir')):
for filename in walk_files:
if root == python_cache or 'minified' in filename: continue
if root == python_cache: continue
file_path = os.path.join(root, filename)
f = db.query(File).filter(File.path == toUnicode(file_path)).first()
if not f:
@@ -4,7 +4,6 @@ var File = new Class({
var self = this;
if(!file){
self.empty = true;
self.el = new Element('div');
return
}
+2 -4
View File
@@ -38,7 +38,7 @@ class LibraryPlugin(Plugin):
title = LibraryTitle(
title = toUnicode(attrs.get('title')),
simple_title = self.simplifyTitle(attrs.get('title')),
simple_title = self.simplifyTitle(attrs.get('title'))
)
l.titles.append(title)
@@ -96,7 +96,6 @@ class LibraryPlugin(Plugin):
titles = info.get('titles', [])
log.debug('Adding titles: %s', titles)
counter = 0
for title in titles:
if not title:
continue
@@ -104,10 +103,9 @@ class LibraryPlugin(Plugin):
t = LibraryTitle(
title = title,
simple_title = self.simplifyTitle(title),
default = (len(default_title) == 0 and counter == 0) or len(titles) == 1 or title.lower() == toUnicode(default_title.lower()) or (toUnicode(default_title) == u'' and toUnicode(titles[0]) == title)
default = title.lower() == toUnicode(default_title.lower()) or (toUnicode(default_title) == u'' and toUnicode(titles[0]) == title)
)
library.titles.append(t)
counter += 1
db.commit()
+4 -38
View File
@@ -2,13 +2,11 @@ from couchpotato.api import addApiView
from couchpotato.core.event import fireEvent, addEvent, fireEventAsync
from couchpotato.core.helpers.encoding import ss
from couchpotato.core.helpers.request import jsonified, getParam
from couchpotato.core.helpers.variable import splitString, getTitle
from couchpotato.core.helpers.variable import getTitle, splitString
from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin
from couchpotato.environment import Env
import ctypes
import os
import sys
import time
import traceback
@@ -24,7 +22,6 @@ class Manage(Plugin):
fireEvent('scheduler.interval', identifier = 'manage.update_library', handle = self.updateLibrary, hours = 2)
addEvent('manage.update', self.updateLibrary)
addEvent('manage.diskspace', self.getDiskSpace)
# Add files after renaming
def after_rename(message = None, group = {}):
@@ -138,6 +135,7 @@ class Manage(Plugin):
already_used = used_files.get(release_file['path'])
if already_used:
print already_used, release['id']
if already_used < release['id']:
fireEvent('release.delete', release['id'], single = True) # delete this one
else:
@@ -195,19 +193,15 @@ class Manage(Plugin):
self.in_progress[folder]['to_go'] = self.in_progress[folder]['to_go'] - 1
total = self.in_progress[folder]['total']
movie_dict = fireEvent('movie.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']))
return afterUpdate
def directories(self):
try:
if self.conf('library', default = '').strip():
return splitString(self.conf('library', default = ''), '::')
return splitString(self.conf('library', default = ''), '::')
except:
pass
return []
return []
def scanFilesToLibrary(self, folder = None, files = None):
@@ -218,31 +212,3 @@ class Manage(Plugin):
for group in groups.itervalues():
if group['library'] and group['library'].get('identifier'):
fireEvent('release.add', group = group)
def getDiskSpace(self):
free_space = {}
for folder in self.directories():
size = None
if os.path.isdir(folder):
if os.name == 'nt':
_, total, free = ctypes.c_ulonglong(), ctypes.c_ulonglong(), \
ctypes.c_ulonglong()
if sys.version_info >= (3,) or isinstance(folder, unicode):
fun = ctypes.windll.kernel32.GetDiskFreeSpaceExW #@UndefinedVariable
else:
fun = ctypes.windll.kernel32.GetDiskFreeSpaceExA #@UndefinedVariable
ret = fun(folder, ctypes.byref(_), ctypes.byref(total), ctypes.byref(free))
if ret == 0:
raise ctypes.WinError()
used = total.value - free.value
return [total.value, used, free.value]
else:
s = os.statvfs(folder)
size = [s.f_blocks * s.f_frsize / (1024 * 1024), (s.f_bavail * s.f_frsize) / (1024 * 1024)]
free_space[folder] = size
return free_space
+28 -96
View File
@@ -6,13 +6,11 @@ from couchpotato.core.helpers.request import getParams, jsonified, getParam
from couchpotato.core.helpers.variable import getImdb, splitString
from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin
from couchpotato.core.settings.model import Library, LibraryTitle, Movie, \
Release
from couchpotato.core.settings.model import Library, LibraryTitle, Movie
from couchpotato.environment import Env
from sqlalchemy.orm import joinedload_all
from sqlalchemy.sql.expression import or_, asc, not_, desc
from sqlalchemy.sql.expression import or_, asc, not_
from string import ascii_lowercase
import time
log = CPLog(__name__)
@@ -43,7 +41,6 @@ class MoviePlugin(Plugin):
'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'},
@@ -97,34 +94,6 @@ class MoviePlugin(Plugin):
addEvent('movie.list', self.list)
addEvent('movie.restatus', self.restatus)
# Clean releases that didn't have activity in the last week
addEvent('app.load', self.cleanReleases)
fireEvent('schedule.interval', 'movie.clean_releases', self.cleanReleases, hours = 4)
def cleanReleases(self):
log.debug('Removing releases from dashboard')
now = time.time()
week = 262080
done_status = fireEvent('status.get', 'done', single = True)
available_status = fireEvent('status.get', 'available', single = True)
snatched_status = fireEvent('status.get', 'snatched', single = True)
db = get_session()
# get movies last_edit more than a week ago
movies = db.query(Movie) \
.filter(Movie.status_id == done_status.get('id'), Movie.last_edit < (now - week)) \
.all()
#
for movie in movies:
for rel in movie.releases:
if rel.status_id in [available_status.get('id'), snatched_status.get('id')]:
fireEvent('release.delete', id = rel.id, single = True)
def getView(self):
movie_id = getParam('id')
@@ -152,29 +121,20 @@ class MoviePlugin(Plugin):
return results
def list(self, status = None, release_status = None, limit_offset = None, starts_with = None, search = None, order = None):
def list(self, status = ['active'], limit_offset = None, starts_with = None, search = None):
db = get_session()
# Make a list from string
if status and not isinstance(status, (list, tuple)):
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(Movie) \
.outerjoin(Movie.releases, Movie.library, Library.titles) \
.join(Movie.library, Library.titles) \
.filter(LibraryTitle.default == True) \
.filter(or_(*[Movie.status.has(identifier = s) for s in status])) \
.group_by(Movie.id)
# Filter on movie status
if status and len(status) > 0:
q = q.filter(or_(*[Movie.status.has(identifier = s) for s in status]))
# Filter on release status
if release_status and len(release_status) > 0:
q = q.filter(or_(*[Release.status.has(identifier = s) for s in release_status]))
total_count = q.count()
filter_or = []
@@ -194,10 +154,7 @@ class MoviePlugin(Plugin):
if filter_or:
q = q.filter(or_(*filter_or))
if order == 'release_order':
q = q.order_by(desc(Release.last_edit))
else:
q = q.order_by(asc(LibraryTitle.simple_title))
q = q.order_by(asc(LibraryTitle.simple_title))
q = q.subquery()
q2 = db.query(Movie).join((q, q.c.id == Movie.id)) \
@@ -209,7 +166,7 @@ class MoviePlugin(Plugin):
.options(joinedload_all('files'))
if limit_offset:
splt = splitString(limit_offset) if isinstance(limit_offset, (str, unicode)) else limit_offset
splt = splitString(limit_offset)
limit = splt[0]
offset = 0 if len(splt) is 1 else splt[1]
q2 = q2.limit(limit).offset(offset)
@@ -228,7 +185,7 @@ class MoviePlugin(Plugin):
#db.close()
return (total_count, movies)
def availableChars(self, status = None, release_status = None):
def availableChars(self, status = ['active']):
chars = ''
@@ -237,20 +194,11 @@ class MoviePlugin(Plugin):
# 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(Movie) \
.outerjoin(Movie.releases, Movie.library, Library.titles, Movie.status) \
.options(joinedload_all('library.titles'))
# Filter on movie status
if status and len(status) > 0:
q = q.filter(or_(*[Movie.status.has(identifier = s) for s in status]))
# Filter on release status
if release_status and len(release_status) > 0:
q = q.filter(or_(*[Release.status.has(identifier = s) for s in release_status]))
.join(Movie.library, Library.titles, Movie.status) \
.options(joinedload_all('library.titles')) \
.filter(or_(*[Movie.status.has(identifier = s) for s in status]))
results = q.all()
@@ -258,29 +206,20 @@ class MoviePlugin(Plugin):
char = movie.library.titles[0].simple_title[0]
char = char if char in ascii_lowercase else '#'
if char not in chars:
chars += str(char)
chars += char
#db.close()
return ''.join(sorted(chars, key = str.lower))
return chars
def listView(self):
params = getParams()
status = splitString(params.get('status', None))
release_status = splitString(params.get('release_status', None))
status = params.get('status', ['active'])
limit_offset = params.get('limit_offset', None)
starts_with = params.get('starts_with', None)
search = params.get('search', None)
order = params.get('order', None)
total_movies, movies = self.list(
status = status,
release_status = release_status,
limit_offset = limit_offset,
starts_with = starts_with,
search = search,
order = order
)
total_movies, movies = self.list(status = status, limit_offset = limit_offset, starts_with = starts_with, search = search)
return jsonified({
'success': True,
@@ -292,9 +231,8 @@ class MoviePlugin(Plugin):
def charView(self):
params = getParams()
status = splitString(params.get('status', None))
release_status = splitString(params.get('release_status', None))
chars = self.availableChars(status, release_status)
status = params.get('status', ['active'])
chars = self.availableChars(status)
return jsonified({
'success': True,
@@ -345,7 +283,7 @@ class MoviePlugin(Plugin):
'movies': movies,
})
def add(self, params = {}, force_readd = True, search_after = True, update_library = False, status_id = None):
def add(self, params = {}, force_readd = True, search_after = True, update_library = False):
if not params.get('identifier'):
msg = 'Can\'t add movie without imdb identifier.'
@@ -354,8 +292,9 @@ class MoviePlugin(Plugin):
return False
else:
try:
is_movie = fireEvent('movie.is_movie', identifier = params.get('identifier'), single = True)
if not is_movie:
url = 'http://thetvdb.com/api/GetSeriesByRemoteID.php?imdbid=%s' % params.get('identifier')
tvdb = self.getCache('thetvdb.%s' % params.get('identifier'), url = url, show_error = False)
if tvdb and 'series' in tvdb.lower():
msg = 'Can\'t add movie, seems to be a TV show.'
log.error(msg)
fireEvent('notify.frontend', type = 'movie.is_tvshow', message = msg)
@@ -368,9 +307,7 @@ class MoviePlugin(Plugin):
# Status
status_active = fireEvent('status.add', 'active', single = True)
snatched_status = fireEvent('status.add', 'snatched', single = True)
ignored_status = fireEvent('status.add', 'ignored', single = True)
downloaded_status = fireEvent('status.add', 'downloaded', single = True)
status_snatched = fireEvent('status.add', 'snatched', single = True)
default_profile = fireEvent('profile.default', single = True)
@@ -382,7 +319,7 @@ class MoviePlugin(Plugin):
m = Movie(
library_id = library.get('id'),
profile_id = params.get('profile_id', default_profile.get('id')),
status_id = status_id if status_id else status_active.get('id'),
status_id = status_active.get('id'),
)
db.add(m)
db.commit()
@@ -394,14 +331,10 @@ class MoviePlugin(Plugin):
fireEventAsync('library.update', params.get('identifier'), default_title = params.get('title', ''), on_complete = onComplete)
search_after = False
elif force_readd:
# Clean snatched history
for release in m.releases:
if release.status_id in [downloaded_status.get('id'), snatched_status.get('id')]:
if params.get('ignore_previous', False):
release.status_id = ignored_status.get('id')
else:
fireEvent('release.delete', release.id, single = True)
if release.status_id == status_snatched.get('id'):
release.delete()
m.profile_id = params.get('profile_id', default_profile.get('id'))
else:
@@ -409,8 +342,7 @@ class MoviePlugin(Plugin):
added = False
if force_readd:
m.status_id = status_id if status_id else status_active.get('id')
m.last_edit = int(time.time())
m.status_id = status_active.get('id')
do_search = True
db.commit()
@@ -516,7 +448,7 @@ class MoviePlugin(Plugin):
total_deleted = 0
new_movie_status = None
for release in movie.releases:
if delete_from in ['wanted', 'snatched']:
if delete_from == 'wanted':
if release.status_id != done_status.get('id'):
db.delete(release)
total_deleted += 1
+46 -59
View File
@@ -5,7 +5,6 @@ var MovieList = new Class({
options: {
navigation: true,
limit: 50,
load_more: true,
menu: [],
add_new: false
},
@@ -13,37 +12,25 @@ var MovieList = new Class({
movies: [],
movies_added: {},
letters: {},
filter: null,
filter: {
'startswith': null,
'search': null
},
initialize: function(options){
var self = this;
self.setOptions(options);
self.offset = 0;
self.filter = self.options.filter || {
'startswith': null,
'search': null
}
self.el = new Element('div.movies').adopt(
self.title = self.options.title ? new Element('h2', {
'text': self.options.title,
'styles': {'display': 'none'}
}) : null,
self.description = self.options.description ? new Element('div.description', {
'html': self.options.description,
'styles': {'display': 'none'}
}) : null,
self.movie_list = new Element('div'),
self.load_more = self.options.load_more ? new Element('a.load_more', {
self.load_more = new Element('a.load_more', {
'events': {
'click': self.loadMore.bind(self)
}
}) : null
})
);
self.changeView(self.getSavedView() || self.options.view || 'details');
self.getMovies();
App.addEvent('movie.added', self.movieAdded.bind(self))
@@ -83,14 +70,22 @@ var MovieList = new Class({
if(self.options.navigation)
self.createNavigation();
if(self.options.load_more)
self.scrollspy = new ScrollSpy({
min: function(){
var c = self.load_more.getCoordinates()
return c.top - window.document.getSize().y - 300
},
onEnter: self.loadMore.bind(self)
});
self.movie_list.addEvents({
'mouseenter:relay(.movie)': function(e, el){
el.addClass('hover');
},
'mouseleave:relay(.movie)': function(e, el){
el.removeClass('hover');
}
});
self.scrollspy = new ScrollSpy({
min: function(){
var c = self.load_more.getCoordinates()
return c.top - window.document.getSize().y - 300
},
onEnter: self.loadMore.bind(self)
});
self.created = true;
},
@@ -101,7 +96,7 @@ var MovieList = new Class({
if(!self.created) self.create();
// do scrollspy
if(movies.length < self.options.limit && self.scrollspy){
if(movies.length < self.options.limit){
self.load_more.hide();
self.scrollspy.stop();
}
@@ -126,14 +121,18 @@ var MovieList = new Class({
createMovie: function(movie, inject_at){
var self = this;
// Attach proper actions
var a = self.options.actions,
status = Status.get(movie.status_id);
var actions = a[status.identifier.capitalize()] || a.Wanted || {};
var m = new Movie(self, {
'actions': self.options.actions,
'actions': actions,
'view': self.current_view,
'onSelect': self.calculateSelected.bind(self)
}, movie);
$(m).inject(self.movie_list, inject_at || 'bottom');
m.fireEvent('injected');
self.movies.include(m)
@@ -217,7 +216,7 @@ var MovieList = new Class({
});
// Actions
['mass_edit', 'details', 'list'].each(function(view){
['mass_edit', 'thumbs', 'list'].each(function(view){
self.navigation_actions.adopt(
new Element('li.'+view+(self.current_view == view ? '.active' : '')+'[data-view='+view+']', {
'events': {
@@ -399,16 +398,11 @@ var MovieList = new Class({
var self = this;
self.movies = []
if(self.mass_edit_select)
self.calculateSelected()
if(self.navigation_alpha)
self.navigation_alpha.getElements('.active').removeClass('active')
self.calculateSelected()
self.navigation_alpha.getElements('.active').removeClass('active')
self.offset = 0;
if(self.scrollspy){
self.load_more.show();
self.scrollspy.start();
}
self.load_more.show();
self.scrollspy.start();
},
activateLetter: function(letter){
@@ -424,6 +418,10 @@ var MovieList = new Class({
changeView: function(new_view){
var self = this;
self.movies.each(function(movie){
movie.changeView(new_view)
});
self.el
.removeClass(self.current_view+'_list')
.addClass(new_view+'_list')
@@ -434,7 +432,7 @@ var MovieList = new Class({
getSavedView: function(){
var self = this;
return Cookie.read(self.options.identifier+'_view') || 'details';
return Cookie.read(self.options.identifier+'_view') || 'thumbs';
},
search: function(){
@@ -470,12 +468,9 @@ var MovieList = new Class({
getMovies: function(){
var self = this;
if(self.scrollspy){
self.scrollspy.stop();
self.load_more.set('text', 'loading...');
}
Api.request(self.options.api_call || 'movie.list', {
if(self.scrollspy) self.scrollspy.stop();
self.load_more.set('text', 'loading...');
Api.request('movie.list', {
'data': Object.merge({
'status': self.options.status,
'limit_offset': self.options.limit + ',' + self.offset
@@ -483,10 +478,8 @@ var MovieList = new Class({
'onComplete': function(json){
self.store(json.movies);
self.addMovies(json.movies, json.total);
if(self.scrollspy) {
self.load_more.set('text', 'load more movies');
self.scrollspy.start();
}
self.load_more.set('text', 'load more movies');
if(self.scrollspy) self.scrollspy.start();
self.checkIfEmpty()
}
@@ -509,13 +502,7 @@ var MovieList = new Class({
checkIfEmpty: function(){
var self = this;
var is_empty = self.movies.length == 0 && (self.total_movies == 0 || self.total_movies === undefined);
if(self.title)
self.title[is_empty ? 'hide' : 'show']()
if(self.description)
self.description[is_empty ? 'hide' : 'show']()
var is_empty = self.movies.length == 0 && self.total_movies == 0;
if(is_empty && self.options.on_empty_element){
self.el.grab(self.options.on_empty_element);
@@ -1,699 +0,0 @@
var MovieAction = new Class({
class_name: 'action icon',
initialize: function(movie){
var self = this;
self.movie = movie;
self.create();
if(self.el)
self.el.addClass(self.class_name)
},
create: function(){},
disable: function(){
this.el.addClass('disable')
},
enable: function(){
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
}
});
var MA = {};
MA.IMDB = new Class({
Extends: MovieAction,
id: null,
create: function(){
var self = this;
self.id = self.movie.get('identifier');
self.el = new Element('a.imdb', {
'title': 'Go to the IMDB page of ' + self.movie.getTitle(),
'href': 'http://www.imdb.com/title/'+self.id+'/',
'target': '_blank'
});
if(!self.id) self.disable();
}
});
MA.Release = new Class({
Extends: MovieAction,
create: function(){
var self = this;
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)
}
});
if(self.movie.data.releases.length == 0){
self.el.hide()
}
else {
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((self.next_release && (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;
if(e)
(e).preventDefault();
if(!self.options_container){
self.options_container = new Element('div.options').adopt(
self.release_container = new Element('div.releases.table').adopt(
self.trynext_container = new Element('div.buttons.try_container')
)
).inject(self.movie, 'top');
// Header
new Element('div.item.head').adopt(
new Element('span.name', {'text': 'Release name'}),
new Element('span.status', {'text': 'Status'}),
new Element('span.quality', {'text': 'Quality'}),
new Element('span.size', {'text': 'Size'}),
new Element('span.age', {'text': 'Age'}),
new Element('span.score', {'text': 'Score'}),
new Element('span.provider', {'text': 'Provider'})
).inject(self.release_container)
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,
provider = self.get(release, 'provider') + (release.info['provider_extra'] ? self.get(release, 'provider_extra') : '');
release.status = status;
var release_name = self.get(release, 'name');
if(release.files && release.files.length > 0){
try {
var movie_file = release.files.filter(function(file){
var type = File.Type.get(file.type_id);
return type && type.identifier == 'movie'
}).pick();
release_name = movie_file.path.split(Api.getOption('path_sep')).getLast();
}
catch(e){}
}
// Create release
new Element('div', {
'class': 'item '+status.identifier,
'id': 'release_'+release.id
}).adopt(
new Element('span.name', {'text': release_name, 'title': release_name}),
new Element('span.status', {'text': status.identifier, 'class': 'release_status '+status.identifier}),
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': provider, 'title': provider }),
release.info['detail_url'] ? new Element('a.info.icon', {
'href': release.info['detail_url'],
'target': '_blank'
}) : null,
new Element('a.download.icon', {
'events': {
'click': function(e){
(e).preventDefault();
if(!this.hasClass('completed'))
self.download(release);
}
}
}),
new Element('a.delete.icon', {
'events': {
'click': function(e){
(e).preventDefault();
self.ignore(release);
this.getParent('.item').toggleClass('ignored')
}
}
})
).inject(self.release_container)
if(status.identifier == 'ignored' || status.identifier == 'failed' || status.identifier == 'snatched'){
if(!self.last_release || (self.last_release && self.last_release.status.identifier != 'snatched' && status.identifier == 'snatched'))
self.last_release = release;
}
else if(!self.next_release && status.identifier == 'available'){
self.next_release = release;
}
});
if(self.last_release){
self.release_container.getElement('#release_'+self.last_release.id).addClass('last_release');
}
if(self.next_release){
self.release_container.getElement('#release_'+self.next_release.id).addClass('next_release');
}
if(self.next_release || self.last_release){
self.trynext_container.adopt(
new Element('span.or', {
'text': 'This movie is snatched, if anything went wrong, 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': ','
}) : 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){
return release.info[type] || 'n/a'
},
download: function(release){
var self = this;
var release_el = self.release_container.getElement('#release_'+release.id),
icon = release_el.getElement('.download.icon');
icon.addClass('spinner');
Api.request('release.download', {
'data': {
'id': release.id
},
'onComplete': function(json){
icon.removeClass('spinner')
if(json.success)
icon.addClass('completed');
else
icon.addClass('attention').set('title', 'Something went wrong when downloading, please check logs.');
}
});
},
ignore: function(release){
var self = this;
Api.request('release.ignore', {
'data': {
'id': release.id
}
})
},
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);
}
});
MA.Trailer = new Class({
Extends: MovieAction,
id: null,
create: function(){
var self = this;
self.el = new Element('a.trailer', {
'title': 'Watch the trailer of ' + self.movie.getTitle(),
'events': {
'click': self.watch.bind(self)
}
});
},
watch: function(offset){
var self = this;
var data_url = 'http://gdata.youtube.com/feeds/videos?vq="{title}" {year} trailer&max-results=1&alt=json-in-script&orderby=relevance&sortorder=descending&format=5&fmt=18'
var url = data_url.substitute({
'title': encodeURI(self.movie.getTitle()),
'year': self.movie.get('year'),
'offset': offset || 1
}),
size = $(self.movie).getSize(),
height = (size.x/16)*9,
id = 'trailer-'+randomString();
self.player_container = new Element('div[id='+id+']');
self.container = new Element('div.hide.trailer_container')
.adopt(self.player_container)
.inject($(self.movie), 'top');
self.container.setStyle('height', 0);
self.container.removeClass('hide');
self.close_button = new Element('a.hide.hide_trailer', {
'text': 'Hide trailer',
'events': {
'click': self.stop.bind(self)
}
}).inject(self.movie);
self.container.setStyle('height', height);
$(self.movie).setStyle('height', height);
new Request.JSONP({
'url': url,
'onComplete': function(json){
var video_url = json.feed.entry[0].id.$t.split('/'),
video_id = video_url[video_url.length-1];
self.player = new YT.Player(id, {
'height': height,
'width': size.x,
'videoId': video_id,
'playerVars': {
'autoplay': 1,
'showsearch': 0,
'wmode': 'transparent',
'iv_load_policy': 3
}
});
self.close_button.removeClass('hide');
var quality_set = false;
var change_quality = function(state){
if(!quality_set && (state.data == 1 || state.data || 2)){
try {
self.player.setPlaybackQuality('hd720');
quality_set = true;
}
catch(e){
}
}
}
self.player.addEventListener('onStateChange', change_quality);
}
}).send()
},
stop: function(){
var self = this;
self.player.stopVideo();
self.container.addClass('hide');
self.close_button.addClass('hide');
$(self.movie).setStyle('height', null);
setTimeout(function(){
self.container.destroy()
self.close_button.destroy();
}, 1800)
}
});
MA.Edit = new Class({
Extends: MovieAction,
create: function(){
var self = this;
self.el = new Element('a.edit', {
'title': 'Change movie information, like title and quality.',
'events': {
'click': self.editMovie.bind(self)
}
});
},
editMovie: function(e){
var self = this;
(e).preventDefault();
if(!self.options_container){
self.options_container = new Element('div.options').adopt(
new Element('div.form').adopt(
self.title_select = new Element('select', {
'name': 'title'
}),
self.profile_select = new Element('select', {
'name': 'profile'
}),
new Element('a.button.edit', {
'text': 'Save & Search',
'events': {
'click': self.save.bind(self)
}
})
)
).inject(self.movie, 'top');
Array.each(self.movie.data.library.titles, function(alt){
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,
'text': profile.label ? profile.label : profile.data.label
}).inject(self.profile_select);
if(self.movie.profile && self.movie.profile.data && self.movie.profile.data.id == profile_id)
self.profile_select.set('value', profile_id);
});
}
self.movie.slide('in', self.options_container);
},
save: function(e){
(e).preventDefault();
var self = this;
Api.request('movie.edit', {
'data': {
'id': self.movie.get('id'),
'default_title': self.title_select.get('value'),
'profile_id': self.profile_select.get('value')
},
'useSpinner': true,
'spinnerTarget': $(self.movie),
'onComplete': function(){
self.movie.quality.set('text', self.profile_select.getSelected()[0].get('text'));
self.movie.title.set('text', self.title_select.getSelected()[0].get('text'));
}
});
self.movie.slide('out');
}
})
MA.Refresh = new Class({
Extends: MovieAction,
create: function(){
var self = this;
self.el = new Element('a.refresh', {
'title': 'Refresh the movie info and do a forced search',
'events': {
'click': self.doRefresh.bind(self)
}
});
},
doRefresh: function(e){
var self = this;
(e).preventDefault();
Api.request('movie.refresh', {
'data': {
'id': self.movie.get('id')
}
});
}
});
MA.Readd = new Class({
Extends: MovieAction,
create: function(){
var self = this;
var movie_done = Status.get(self.movie.data.status_id).identifier == 'done';
if(!movie_done)
var snatched = self.movie.data.releases.filter(function(release){
return release.status && (release.status.identifier == 'snatched' || release.status.identifier == 'downloaded' || release.status.identifier == 'done');
}).length;
if(movie_done || snatched && snatched > 0)
self.el = new Element('a.readd', {
'title': 'Readd the movie and mark all previous snatched/downloaded as ignored',
'events': {
'click': self.doReadd.bind(self)
}
});
},
doReadd: function(e){
var self = this;
(e).preventDefault();
Api.request('movie.add', {
'data': {
'identifier': self.movie.get('identifier'),
'ignore_previous': 1
}
});
}
});
MA.Delete = new Class({
Extends: MovieAction,
Implements: [Chain],
create: function(){
var self = this;
self.el = new Element('a.delete', {
'title': 'Remove the movie from this CP list',
'events': {
'click': self.showConfirm.bind(self)
}
});
},
showConfirm: function(e){
var self = this;
(e).preventDefault();
if(!self.delete_container){
self.delete_container = new Element('div.buttons.delete_container').adopt(
new Element('a.cancel', {
'text': 'Cancel',
'events': {
'click': self.hideConfirm.bind(self)
}
}),
new Element('span.or', {
'text': 'or'
}),
new Element('a.button.delete', {
'text': 'Delete ' + self.movie.title.get('text'),
'events': {
'click': self.del.bind(self)
}
})
).inject(self.movie, 'top');
}
self.movie.slide('in', self.delete_container);
},
hideConfirm: function(e){
var self = this;
(e).preventDefault();
self.movie.slide('out');
},
del: function(e){
(e).preventDefault();
var self = this;
var movie = $(self.movie);
self.chain(
function(){
self.callChain();
},
function(){
Api.request('movie.delete', {
'data': {
'id': self.movie.get('id'),
'delete_from': self.movie.list.options.identifier
},
'onComplete': function(){
movie.set('tween', {
'duration': 300,
'onComplete': function(){
self.movie.destroy()
}
});
movie.tween('height', 0);
}
});
}
);
self.callChain();
}
});
MA.Files = new Class({
Extends: MovieAction,
create: function(){
var self = this;
self.el = new Element('a.directory', {
'title': 'Available files',
'events': {
'click': self.showFiles.bind(self)
}
});
},
showFiles: function(e){
var self = this;
(e).preventDefault();
if(!self.options_container){
self.options_container = new Element('div.options').adopt(
self.files_container = new Element('div.files.table')
).inject(self.movie, 'top');
// Header
new Element('div.item.head').adopt(
new Element('span.name', {'text': 'File'}),
new Element('span.type', {'text': 'Type'}),
new Element('span.is_available', {'text': 'Available'})
).inject(self.files_container)
Array.each(self.movie.data.releases, function(release){
var rel = new Element('div.release').inject(self.files_container);
Array.each(release.files, function(file){
new Element('div.file.item').adopt(
new Element('span.name', {'text': file.path}),
new Element('span.type', {'text': File.Type.get(file.type_id).name}),
new Element('span.available', {'text': file.available})
).inject(rel)
});
});
}
self.movie.slide('in', self.options_container);
},
});
+83 -228
View File
@@ -1,33 +1,7 @@
.movies {
padding: 60px 0 20px;
position: relative;
z-index: 3;
}
.movies h2 {
margin-bottom: 20px;
}
.movies > .description {
position: absolute;
top: 30px;
right: 0;
font-style: italic;
text-shadow: none;
opacity: 0.8;
}
.movies:hover > .description {
opacity: 1;
}
.movies.thumbs_list {
padding: 20px 0 20px;
}
.home .movies {
padding-top: 6px;
}
.movies.mass_edit_list {
padding-top: 90px;
}
@@ -38,58 +12,33 @@
margin: 10px 0;
overflow: hidden;
width: 100%;
height: 180px;
transition: all 0.2s linear;
}
.movies.list_list .movie:not(.details_view),
.movies.mass_edit_list .movie {
height: 32px;
}
.movies.thumbs_list .movie {
width: 153px;
height: 230px;
display: inline-block;
margin: 0 8px 0 0;
}
.movies.thumbs_list .movie:nth-child(6n+6) {
margin: 0;
}
.movies .movie .mask {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
}
.movies.list_list .movie:not(.details_view),
.movies.mass_edit_list .movie {
.movies .movie.list_view, .movies .movie.mass_edit_view {
margin: 1px 0;
border-radius: 0;
background: no-repeat;
box-shadow: none;
border-bottom: 1px solid rgba(255,255,255,0.05);
}
.movies.list_list .movie:hover:not(.details_view),
.movies.mass_edit_list .movie {
.movies .movie.list_view:hover, .movies .movie.mass_edit_view:hover {
background: rgba(255,255,255,0.03);
}
.movies .movie_container {
overflow: hidden;
}
.movies .data {
padding: 20px;
height: 100%;
height: 180px;
width: 840px;
position: absolute;
right: 0;
position: relative;
float: right;
border-radius: 0;
transition: all .6s cubic-bezier(0.9,0,0.1,1);
transition: all 0.2s linear;
}
.movies.list_list .movie:not(.details_view) .data,
.movies.mass_edit_list .movie .data {
.movies .list_view .data, .movies .mass_edit_view .data {
height: 30px;
padding: 3px 0 3px 10px;
width: 938px;
@@ -97,148 +46,79 @@
border: 0;
background: none;
}
.movies.thumbs_list .data {
left: 0;
width: 100%;
padding: 10px;
height: 100%;
background: none;
transition: none;
}
.movies.thumbs_list .movie.no_thumbnail .data { background-image: linear-gradient(-30deg, rgba(255, 0, 85, .2) 0,rgba(125, 185, 235, .2) 100%);
}
.movies.thumbs_list .movie.no_thumbnail:nth-child(2n+6) .data { background-image: linear-gradient(-20deg, rgba(125, 0, 215, .2) 0, rgba(4, 55, 5, .7) 100%); }
.movies.thumbs_list .movie.no_thumbnail:nth-child(3n+6) .data { background-image: linear-gradient(-30deg, rgba(155, 0, 85, .2) 0,rgba(25, 185, 235, .7) 100%); }
.movies.thumbs_list .movie.no_thumbnail:nth-child(4n+6) .data { background-image: linear-gradient(-30deg, rgba(115, 5, 235, .2) 0, rgba(55, 180, 5, .7) 100%); }
.movies.thumbs_list .movie.no_thumbnail:nth-child(5n+6) .data { background-image: linear-gradient(-30deg, rgba(35, 15, 215, .2) 0, rgba(135, 215, 115, .7) 100%); }
.movies.thumbs_list .movie.no_thumbnail:nth-child(6n+6) .data { background-image: linear-gradient(-30deg, rgba(35, 15, 215, .2) 0, rgba(135, 15, 115, .7) 100%); }
.movies.thumbs_list .movie:hover .data {
background: rgba(0,0,0,0.9);
}
.movies .data.hide_right {
right: -100%;
}
.movies .movie .check {
display: none;
}
.movies.mass_edit_list .movie .check {
position: absolute;
left: 0;
top: 0;
float: left;
display: block;
margin: 7px 0 0 5px;
}
.movies .poster {
position: absolute;
left: 0;
float: left;
width: 120px;
line-height: 0;
overflow: hidden;
height: 100%;
height: 180px;
border-radius: 4px 0 0 4px;
transition: all .6s cubic-bezier(0.9,0,0.1,1);
transition: all 0.2s linear;
}
.movies.list_list .movie:not(.details_view) .poster,
.movies.mass_edit_list .poster {
.movies .list_view .poster, .movies .mass_edit_view .poster {
width: 20px;
height: 30px;
border-radius: 1px 0 0 1px;
}
.movies.mass_edit_list .poster {
display: none;
}
.movies.thumbs_list .poster {
width: 100%;
height: 100%;
}
.movies .poster img,
.options .poster img {
.movies .poster img, .options .poster img {
width: 101%;
height: 101%;
}
.movies .info {
position: relative;
height: 100%;
}
.movies .info .title {
display: inline;
position: absolute;
font-size: 28px;
font-size: 30px;
font-weight: bold;
margin-bottom: 10px;
left: 0;
top: 0;
float: left;
width: 90%;
transition: all 0.2s linear;
}
.movies.list_list .movie:not(.details_view) .info .title,
.movies.mass_edit_list .info .title {
.movies .list_view .info .title, .movies .mass_edit_view .info .title {
font-size: 16px;
font-weight: normal;
text-overflow: ellipsis;
width: auto;
overflow: hidden;
}
.movies.thumbs_list .movie:not(.no_thumbnail) .info {
display: none;
}
.movies.thumbs_list .movie:hover .info {
display: block;
}
.movies.thumbs_list .info .title {
font-size: 21px;
text-shadow: 0 0 10px #000;
word-wrap: break-word;
}
.movies .info .year {
position: absolute;
font-size: 30px;
margin-bottom: 10px;
float: right;
color: #bbb;
width: 10%;
right: 0;
top: 0;
text-align: right;
transition: all 0.2s linear;
}
.movies.list_list .movie:not(.details_view) .info .year,
.movies.mass_edit_list .info .year {
.movies .list_view .info .year, .movies .mass_edit_view .info .year {
font-size: 16px;
width: 6%;
right: 10px;
}
.movies.thumbs_list .info .year {
font-size: 23px;
margin: 0;
bottom: 0;
left: 0;
top: auto;
right: auto;
color: #FFF;
text-shadow: none;
text-shadow: 0 0 6px #000;
}
.movies .info .rating {
font-size: 30px;
margin-bottom: 10px;
color: #444;
float: left;
width: 5%;
padding: 0 0 0 3%;
}
.movies .info .description {
position: absolute;
top: 30px;
clear: both;
height: 80px;
overflow: hidden;
@@ -246,82 +126,63 @@
.movies .data:hover .description {
overflow: auto;
}
.movies.list_list .movie:not(.details_view) .info .description,
.movies.mass_edit_list .info .description,
.movies.thumbs_list .info .description {
.movies .list_view .info .description, .movies .mass_edit_view .info .description {
display: none;
}
.movies .data .quality {
position: absolute;
bottom: 0;
display: block;
min-height: 20px;
vertical-align: mid;
}
.movies .status_suggest .data .quality,
.movies.thumbs_list .data .quality {
display: none;
.movies .data .quality span {
padding: 2px 3px;
font-weight: bold;
opacity: 0.5;
font-size: 10px;
height: 16px;
line-height: 12px;
vertical-align: middle;
display: inline-block;
text-transform: uppercase;
text-shadow: none;
font-weight: normal;
margin: 0 2px;
border-radius: 2px;
background-color: rgba(255,255,255,0.1);
}
.movies .list_view .data .quality, .movies .mass_edit_view .data .quality {
text-align: right;
float: right;
}
.movies .data .quality span {
padding: 2px 3px;
font-weight: bold;
opacity: 0.5;
font-size: 10px;
height: 16px;
line-height: 12px;
vertical-align: middle;
display: inline-block;
text-transform: uppercase;
text-shadow: none;
font-weight: normal;
margin: 0 2px;
border-radius: 2px;
background-color: rgba(255,255,255,0.1);
}
.movies.list_list .data .quality,
.movies.mass_edit_list .data .quality {
text-align: right;
right: 0;
margin-right: 50px;
z-index: 1;
}
.movies .data .quality .available,
.movies .data .quality .snatched {
opacity: 1;
box-shadow: 1px 1px 0 rgba(0,0,0,0.2);
cursor: pointer;
}
.movies .data .quality .available { background-color: #578bc3; }
.movies .data .quality .snatched { background-color: #369545; }
.movies .data .quality .done {
background-color: #369545;
opacity: 1;
}
.movies .data .quality .finish {
background-image: url('../images/sprite.png');
background-repeat: no-repeat;
background-position: 0 2px;
padding-left: 14px;
background-size: 14px
}
.movies .data .quality .available, .movies .data .quality .snatched {
opacity: 1;
box-shadow: 1px 1px 0 rgba(0,0,0,0.2);
cursor: pointer;
}
.movies .data .quality .available { background-color: #578bc3; }
.movies .data .quality .snatched { background-color: #369545; }
.movies .data .quality .done {
background-color: #369545;
opacity: 1;
}
.movies .data .quality .finish {
background-image: url('../images/sprite.png');
background-repeat: no-repeat;
background-position: 0 2px;
padding-left: 14px;
background-size: 14px
}
.movies .data .actions {
position: absolute;
bottom: 20px;
right: 20px;
line-height: 0;
clear: both;
float: right;
margin-top: -25px;
}
.movies.thumbs_list .data .actions {
bottom: 8px;
right: 10px;
}
.movies .data:hover .action { opacity: 0.6; }
.movies .data:hover .action:hover { opacity: 1; }
.movies.mass_edit_list .data .actions {
@@ -338,14 +199,10 @@
opacity: 0;
}
.movies.list_list .movie:not(.details_view) .data:hover .actions,
.movies.mass_edit_list .data:hover .actions {
margin: 0;
.movies .list_view .data:hover .actions, .movies .mass_edit_view .data:hover .actions {
margin: -34px 2px 0 0;
background: #4e5969;
top: 2px;
bottom: 2px;
right: 5px;
z-index: 3;
position: relative;
}
.movies .delete_container {
@@ -427,7 +284,6 @@
.movies .options .table .provider {
width: 120px;
text-overflow: ellipsis;
overflow: hidden;
}
.movies .options .table .name {
width: 350px;
@@ -479,11 +335,11 @@
padding: 3px 10px;
background: #4e5969;
border-radius: 0 0 2px 2px;
transition: all .2s cubic-bezier(0.9,0,0.1,1) .2s;
transition: all .6s cubic-bezier(0.9,0,0.1,1) .2s;
}
.movies .movie .hide_trailer.hide {
top: -30px;
}
.movies .movie .hide_trailer.hide {
top: -30px;
}
.movies .movie .try_container {
padding: 5px 10px;
@@ -524,7 +380,7 @@
.movies .alph_nav {
transition: box-shadow .4s linear;
position: fixed;
z-index: 4;
z-index: 2;
top: 0;
padding: 100px 60px 7px;
width: 1080px;
@@ -553,8 +409,7 @@
text-align: center;
}
.movies .alph_nav .numbers li,
.movies .alph_nav .actions li {
.movies .alph_nav .numbers li, .movies .alph_nav .actions li {
display: inline-block;
vertical-align: top;
width: 20px;
@@ -617,7 +472,7 @@
background-position: 3px -95px;
}
.movies .alph_nav .actions li.details span {
.movies .alph_nav .actions li.thumbs span {
background-position: 3px -74px;
}
+437 -55
View File
@@ -8,7 +8,7 @@ var Movie = new Class({
var self = this;
self.data = data;
self.view = options.view || 'details';
self.view = options.view || 'thumbs';
self.list = list;
self.el = new Element('div.movie.inlay');
@@ -72,6 +72,7 @@ var Movie = new Class({
else if(!self.spinner) {
self.createMask();
self.spinner = createSpinner(self.mask);
self.positionMask();
self.mask.fade('in');
}
},
@@ -80,9 +81,10 @@ var Movie = new Class({
var self = this;
self.mask = new Element('div.mask', {
'styles': {
'z-index': 4
'z-index': '1'
}
}).inject(self.el, 'top').fade('hide');
self.positionMask();
},
positionMask: function(){
@@ -101,7 +103,7 @@ var Movie = new Class({
var self = this;
self.data = notification.data;
self.el.empty();
self.container.destroy();
self.profile = Quality.getProfile(self.data.profile_id) || {};
self.create();
@@ -112,50 +114,52 @@ var Movie = new Class({
create: function(){
var self = this;
var s = Status.get(self.get('status_id'));
self.el.addClass('status_'+s.identifier);
self.el.adopt(
self.select_checkbox = new Element('input[type=checkbox].inlay', {
'events': {
'change': function(){
self.fireEvent('select')
}
}
}),
self.thumbnail = File.Select.single('poster', self.data.library.files),
self.data_container = new Element('div.data.inlay.light').adopt(
self.info_container = new Element('div.info').adopt(
self.title = new Element('div.title', {
'text': self.getTitle() || 'n/a'
}),
self.year = new Element('div.year', {
'text': self.data.library.year || 'n/a'
}),
self.rating = new Element('div.rating.icon', {
'text': self.data.library.rating
}),
self.description = new Element('div.description', {
'text': self.data.library.plot
}),
self.quality = new Element('div.quality', {
'events': {
'click': function(e){
var releases = self.el.getElement('.actions .releases');
if(releases)
releases.fireEvent('click', [e])
}
self.container = new Element('div.movie_container').adopt(
self.select_checkbox = new Element('input[type=checkbox].inlay', {
'events': {
'change': function(){
self.fireEvent('select')
}
})
),
self.actions = new Element('div.actions')
}
}),
self.thumbnail = File.Select.single('poster', self.data.library.files),
self.data_container = new Element('div.data.inlay.light', {
'tween': {
duration: 400,
transition: 'quint:in:out',
onComplete: self.fireEvent.bind(self, 'slideEnd')
}
}).adopt(
self.info_container = new Element('div.info').adopt(
self.title = new Element('div.title', {
'text': self.getTitle() || 'n/a'
}),
self.year = new Element('div.year', {
'text': self.data.library.year || 'n/a'
}),
self.rating = new Element('div.rating.icon', {
'text': self.data.library.rating
}),
self.description = new Element('div.description', {
'text': self.data.library.plot
}),
self.quality = new Element('div.quality', {
'events': {
'click': function(e){
var releases = self.el.getElement('.actions .releases');
if(releases)
releases.fireEvent('click', [e])
}
}
})
),
self.actions = new Element('div.actions')
)
)
);
if(self.thumbnail.empty)
self.el.addClass('no_thumbnail');
//self.changeView(self.view);
self.changeView(self.view);
self.select_checkbox_class = new Form.Check(self.select_checkbox);
// Add profile
@@ -170,7 +174,7 @@ var Movie = new Class({
});
// Add releases
// Add done releases
self.data.releases.each(function(release){
var q = self.quality.getElement('.q_id'+ release.quality_id),
@@ -237,23 +241,23 @@ var Movie = new Class({
if(direction == 'in'){
self.temp_view = self.view;
self.changeView('details')
self.changeView('thumbs')
self.el.addEvent('outerClick', function(){
self.removeView()
self.changeView(self.temp_view)
self.slide('out')
})
el.show();
self.data_container.addClass('hide_right');
self.data_container.tween('right', 0, -840);
}
else {
self.el.removeEvents('outerClick')
setTimeout(function(){
self.addEvent('slideEnd:once', function(){
self.el.getElements('> :not(.data):not(.poster):not(.movie_container)').hide();
}, 600);
});
self.data_container.removeClass('hide_right');
self.data_container.tween('right', -840, 0);
}
},
@@ -267,12 +271,6 @@ var Movie = new Class({
self.view = new_view;
},
removeView: function(){
var self = this;
self.el.removeClass(self.view+'_view')
},
get: function(attr){
return this.data[attr] || this.data.library[attr]
},
@@ -290,4 +288,388 @@ var Movie = new Class({
return this.el;
}
});
var MovieAction = new Class({
class_name: 'action icon',
initialize: function(movie){
var self = this;
self.movie = movie;
self.create();
if(self.el)
self.el.addClass(self.class_name)
},
create: function(){},
disable: function(){
this.el.addClass('disable')
},
enable: function(){
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
}
});
var IMDBAction = new Class({
Extends: MovieAction,
id: null,
create: function(){
var self = this;
self.id = self.movie.get('identifier');
self.el = new Element('a.imdb', {
'title': 'Go to the IMDB page of ' + self.movie.getTitle(),
'href': 'http://www.imdb.com/title/'+self.id+'/',
'target': '_blank'
});
if(!self.id) self.disable();
}
});
var ReleaseAction = new Class({
Extends: MovieAction,
create: function(){
var self = this;
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)
}
});
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((self.next_release && (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;
if(e)
(e).preventDefault();
if(!self.options_container){
self.options_container = new Element('div.options').adopt(
self.release_container = new Element('div.releases.table').adopt(
self.trynext_container = new Element('div.buttons.try_container')
)
).inject(self.movie, 'top');
// Header
new Element('div.item.head').adopt(
new Element('span.name', {'text': 'Release name'}),
new Element('span.status', {'text': 'Status'}),
new Element('span.quality', {'text': 'Quality'}),
new Element('span.size', {'text': 'Size'}),
new Element('span.age', {'text': 'Age'}),
new Element('span.score', {'text': 'Score'}),
new Element('span.provider', {'text': 'Provider'})
).inject(self.release_container)
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;
release.status = status;
// Create release
new Element('div', {
'class': 'item '+status.identifier,
'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.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')}),
release.info['detail_url'] ? new Element('a.info.icon', {
'href': release.info['detail_url'],
'target': '_blank'
}) : null,
new Element('a.download.icon', {
'events': {
'click': function(e){
(e).preventDefault();
if(!this.hasClass('completed'))
self.download(release);
}
}
}),
new Element('a.delete.icon', {
'events': {
'click': function(e){
(e).preventDefault();
self.ignore(release);
this.getParent('.item').toggleClass('ignored')
}
}
})
).inject(self.release_container)
if(status.identifier == 'ignored' || status.identifier == 'failed' || status.identifier == 'snatched'){
if(!self.last_release || (self.last_release && self.last_release.status.identifier != 'snatched' && status.identifier == 'snatched'))
self.last_release = release;
}
else if(!self.next_release && status.identifier == 'available'){
self.next_release = release;
}
});
if(self.last_release){
self.release_container.getElement('#release_'+self.last_release.id).addClass('last_release');
}
if(self.next_release){
self.release_container.getElement('#release_'+self.next_release.id).addClass('next_release');
}
if(self.next_release || self.last_release){
self.trynext_container.adopt(
new Element('span.or', {
'text': 'This movie is snatched, if anything went wrong, 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': ','
}) : 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){
return release.info[type] || 'n/a'
},
download: function(release){
var self = this;
var release_el = self.release_container.getElement('#release_'+release.id),
icon = release_el.getElement('.download.icon');
icon.addClass('spinner');
Api.request('release.download', {
'data': {
'id': release.id
},
'onComplete': function(json){
icon.removeClass('spinner')
if(json.success)
icon.addClass('completed');
else
icon.addClass('attention').set('title', 'Something went wrong when downloading, please check logs.');
}
});
},
ignore: function(release){
var self = this;
Api.request('release.ignore', {
'data': {
'id': release.id
}
})
},
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);
}
});
var TrailerAction = new Class({
Extends: MovieAction,
id: null,
create: function(){
var self = this;
self.el = new Element('a.trailer', {
'title': 'Watch the trailer of ' + self.movie.getTitle(),
'events': {
'click': self.watch.bind(self)
}
});
},
watch: function(offset){
var self = this;
var data_url = 'http://gdata.youtube.com/feeds/videos?vq="{title}" {year} trailer&max-results=1&alt=json-in-script&orderby=relevance&sortorder=descending&format=5&fmt=18'
var url = data_url.substitute({
'title': encodeURI(self.movie.getTitle()),
'year': self.movie.get('year'),
'offset': offset || 1
}),
size = $(self.movie).getSize(),
height = (size.x/16)*9,
id = 'trailer-'+randomString();
self.player_container = new Element('div[id='+id+']');
self.container = new Element('div.hide.trailer_container')
.adopt(self.player_container)
.inject(self.movie.container, 'top');
self.container.setStyle('height', 0);
self.container.removeClass('hide');
self.close_button = new Element('a.hide.hide_trailer', {
'text': 'Hide trailer',
'events': {
'click': self.stop.bind(self)
}
}).inject(self.movie);
setTimeout(function(){
$(self.movie).setStyle('max-height', height);
self.container.setStyle('height', height);
}, 100)
new Request.JSONP({
'url': url,
'onComplete': function(json){
var video_url = json.feed.entry[0].id.$t.split('/'),
video_id = video_url[video_url.length-1];
self.player = new YT.Player(id, {
'height': height,
'width': size.x,
'videoId': video_id,
'playerVars': {
'autoplay': 1,
'showsearch': 0,
'wmode': 'transparent',
'iv_load_policy': 3
}
});
self.close_button.removeClass('hide');
var quality_set = false;
var change_quality = function(state){
if(!quality_set && (state.data == 1 || state.data || 2)){
try {
self.player.setPlaybackQuality('hd720');
quality_set = true;
}
catch(e){
}
}
}
self.player.addEventListener('onStateChange', change_quality);
}
}).send()
},
stop: function(){
var self = this;
self.player.stopVideo();
self.container.addClass('hide');
self.close_button.addClass('hide');
setTimeout(function(){
self.container.destroy()
self.close_button.destroy();
}, 1800)
}
});
@@ -191,8 +191,6 @@
.movie_result .info h2 {
margin: 0;
font-size: 17px;
line-height: 20px;
}
.movie_result .info h2 span {
@@ -202,8 +200,7 @@
.movie_result .info h2 span:before { content: "("; }
.movie_result .info h2 span:after { content: ")"; }
.search_form .mask,
.movie_result .mask {
.search_form .mask {
border-radius: 3px;
position: absolute;
height: 100%;
@@ -366,7 +366,7 @@ Block.Search.Item = new Class({
loadingMask: function(){
var self = this;
self.mask = new Element('div.mask').inject(self.el).fade('hide')
self.mask = new Element('span.mask').inject(self.el).fade('hide')
createSpinner(self.mask)
self.mask.fade('in')
+1 -1
View File
@@ -21,7 +21,7 @@ class QualityPlugin(Plugin):
{'identifier': 'bd50', 'hd': True, 'size': (15000, 60000), 'label': 'BR-Disk', 'alternative': ['bd25'], 'allow': ['1080p'], 'ext':[], 'tags': ['bdmv', 'certificate', ('complete', 'bluray')]},
{'identifier': '1080p', 'hd': True, 'size': (5000, 20000), 'label': '1080P', 'width': 1920, 'height': 1080, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts'], 'tags': ['m2ts']},
{'identifier': '720p', 'hd': True, 'size': (3500, 10000), 'label': '720P', 'width': 1280, 'height': 720, 'alternative': [], 'allow': [], 'ext':['mkv', 'ts']},
{'identifier': 'brrip', 'hd': True, 'size': (700, 7000), 'label': 'BR-Rip', 'alternative': ['bdrip'], 'allow': ['720p', '1080p'], 'ext':['avi']},
{'identifier': 'brrip', 'hd': True, 'size': (700, 7000), 'label': 'BR-Rip', 'alternative': ['bdrip'], 'allow': ['720p'], 'ext':['avi']},
{'identifier': 'dvdr', 'size': (3000, 10000), 'label': 'DVD-R', 'alternative': [], 'allow': [], 'ext':['iso', 'img'], 'tags': ['pal', 'ntsc', 'video_ts', 'audio_ts']},
{'identifier': 'dvdrip', 'size': (600, 2400), 'label': 'DVD-Rip', 'width': 720, 'alternative': ['dvdrip'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg'], 'tags': [('dvd', 'rip'), ('dvd', 'xvid'), ('dvd', 'divx')]},
{'identifier': 'scr', 'size': (600, 1600), 'label': 'Screener', 'alternative': ['screener', 'dvdscr', 'ppvrip'], 'allow': ['dvdr', 'dvd'], 'ext':['avi', 'mpg', 'mpeg']},
@@ -1,5 +1,4 @@
from couchpotato.core.plugins.renamer.main import Renamer
import os
def start():
return Renamer()
@@ -112,15 +111,6 @@ config = [{
'label': 'Separator',
'description': 'Replace all the spaces with a character. Example: ".", "-" (without quotes). Leave empty to use spaces.',
},
{
'advanced': True,
'name': 'ntfs_permission',
'label': 'NTFS Permission',
'type': 'bool',
'hidden': os.name != 'nt',
'description': 'Set permission of moved files to that of destination folder (Windows NTFS only).',
'default': False,
},
],
}, {
'tab': 'renamer',
+6 -14
View File
@@ -13,7 +13,6 @@ import errno
import os
import re
import shutil
import time
import traceback
log = CPLog(__name__)
@@ -171,15 +170,15 @@ class Renamer(Plugin):
replacements['cd_nr'] = cd if multiple else ''
# Naming
final_folder_name = self.doReplace(folder_name, replacements).lstrip('. ')
final_file_name = self.doReplace(file_name, replacements).lstrip('. ')
final_folder_name = self.doReplace(folder_name, replacements)
final_file_name = self.doReplace(file_name, replacements)
replacements['filename'] = final_file_name[:-(len(getExt(final_file_name)) + 1)]
# Meta naming
if file_type is 'trailer':
final_file_name = self.doReplace(trailer_name, replacements, remove_multiple = True).lstrip('. ')
final_file_name = self.doReplace(trailer_name, replacements, remove_multiple = True)
elif file_type is 'nfo':
final_file_name = self.doReplace(nfo_name, replacements, remove_multiple = True).lstrip('. ')
final_file_name = self.doReplace(nfo_name, replacements, remove_multiple = True)
# Seperator replace
if separator:
@@ -276,7 +275,6 @@ class Renamer(Plugin):
for profile_type in movie.profile.types:
if profile_type.quality_id == group['meta_data']['quality']['id'] and profile_type.finish:
movie.status_id = done_status.get('id')
movie.last_edit = int(time.time())
db.commit()
except Exception, e:
log.error('Failed marking movie finished: %s %s', (e, traceback.format_exc()))
@@ -318,10 +316,8 @@ class Renamer(Plugin):
log.debug('Marking release as downloaded')
try:
release.status_id = downloaded_status.get('id')
release.last_edit = int(time.time())
except Exception, e:
log.error('Failed marking release as finished: %s %s', (e, traceback.format_exc()))
db.commit()
# Remove leftover files
@@ -459,8 +455,6 @@ class Renamer(Plugin):
try:
os.chmod(dest, Env.getPermission('file'))
if os.name == 'nt' and self.conf('ntfs_permission'):
os.popen('icacls "' + dest + '"* /reset /T')
except:
log.error('Failed setting permissions for file: %s, %s', (dest, traceback.format_exc(1)))
@@ -474,7 +468,7 @@ class Renamer(Plugin):
except:
log.error('Couldn\'t move file "%s" to "%s": %s', (old, dest, traceback.format_exc()))
raise
raise Exception
return True
@@ -560,7 +554,6 @@ class Renamer(Plugin):
if rel.movie.status_id == done_status.get('id'):
log.debug('Found a completed movie with a snatched release : %s. Setting release status to ignored...' , default_title)
rel.status_id = ignored_status.get('id')
rel.last_edit = int(time.time())
db.commit()
continue
@@ -571,7 +564,7 @@ class Renamer(Plugin):
found = False
for item in statuses:
if item['name'] == nzbname or rel_dict['info']['name'] in item['name'] or getImdb(item['name']) == movie_dict['library']['identifier']:
if item['name'] == nzbname or getImdb(item['name']) == movie_dict['library']['identifier']:
timeleft = 'N/A' if item['timeleft'] == -1 else item['timeleft']
log.debug('Found %s: %s, time to go: %s', (item['name'], item['status'].upper(), timeleft))
@@ -585,7 +578,6 @@ class Renamer(Plugin):
fireEvent('searcher.try_next_release', movie_id = rel.movie_id)
else:
rel.status_id = failed_status.get('id')
rel.last_edit = int(time.time())
db.commit()
elif item['status'] == 'completed':
log.info('Download of %s completed!', item['name'])
+1 -1
View File
@@ -23,7 +23,7 @@ class Scanner(Plugin):
'media': 314572800, # 300MB
'trailer': 1048576, # 1MB
}
ignored_in_path = [os.path.sep + 'extracted' + os.path.sep, 'extracting', '_unpack', '_failed_', '_unknown_', '_exists_', '_failed_remove_', '_failed_rename_', '.appledouble', '.appledb', '.appledesktop', os.path.sep + '._', '.ds_store', 'cp.cpnfo'] #unpacking, smb-crap, hidden files
ignored_in_path = ['extracting', '_unpack', '_failed_', '_unknown_', '_exists_', '_failed_remove_', '_failed_rename_', '.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', 'bdmv', 'certificate']
extensions = {
'movie': ['mkv', 'wmv', 'avi', 'mpg', 'mpeg', 'mp4', 'm2ts', 'iso', 'img', 'mdf', 'ts', 'm4v'],
+68 -141
View File
@@ -12,7 +12,6 @@ from couchpotato.environment import Env
from inspect import ismethod, isfunction
from sqlalchemy.exc import InterfaceError
import datetime
import random
import re
import time
import traceback
@@ -30,7 +29,6 @@ class Searcher(Plugin):
addEvent('searcher.correct_movie', self.correctMovie)
addEvent('searcher.download', self.download)
addEvent('searcher.try_next_release', self.tryNextRelease)
addEvent('searcher.could_be_released', self.couldBeReleased)
addApiView('searcher.try_next', self.tryNextReleaseView, docs = {
'desc': 'Marks the snatched results as ignored and try the next best release',
@@ -85,51 +83,37 @@ class Searcher(Plugin):
movies = db.query(Movie).filter(
Movie.status.has(identifier = 'active')
).all()
random.shuffle(movies)
self.in_progress = {
'total': len(movies),
'to_go': len(movies),
}
try:
search_types = self.getSearchTypes()
for movie in movies:
movie_dict = movie.to_dict({
'profile': {'types': {'quality': {}}},
'releases': {'status': {}, 'quality': {}},
'library': {'titles': {}, 'files':{}},
'files': {}
})
for movie in movies:
movie_dict = movie.to_dict({
'profile': {'types': {'quality': {}}},
'releases': {'status': {}, 'quality': {}},
'library': {'titles': {}, 'files':{}},
'files': {}
})
try:
self.single(movie_dict)
except IndexError:
log.error('Forcing library update for %s, if you see this often, please report: %s', (movie_dict['library']['identifier'], traceback.format_exc()))
fireEvent('library.update', movie_dict['library']['identifier'], force = True)
except:
log.error('Search failed for %s: %s', (movie_dict['library']['identifier'], traceback.format_exc()))
try:
self.single(movie_dict, search_types)
except IndexError:
log.error('Forcing library update for %s, if you see this often, please report: %s', (movie_dict['library']['identifier'], traceback.format_exc()))
fireEvent('library.update', movie_dict['library']['identifier'], force = True)
except:
log.error('Search failed for %s: %s', (movie_dict['library']['identifier'], traceback.format_exc()))
self.in_progress['to_go'] -= 1
self.in_progress['to_go'] -= 1
# Break if CP wants to shut down
if self.shuttingDown():
break
except SearchSetupError:
pass
# Break if CP wants to shut down
if self.shuttingDown():
break
self.in_progress = False
def single(self, movie, search_types = None):
# Find out search type
try:
if not search_types:
search_types = self.getSearchTypes()
except SearchSetupError:
return
def single(self, movie):
done_status = fireEvent('status.get', 'done', single = True)
@@ -144,8 +128,6 @@ class Searcher(Plugin):
available_status = fireEvent('status.get', 'available', single = True)
ignored_status = fireEvent('status.get', 'ignored', single = True)
found_releases = []
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.')
@@ -154,10 +136,9 @@ class Searcher(Plugin):
fireEvent('notify.frontend', type = 'searcher.started.%s' % movie['id'], data = True, message = 'Searching for "%s"' % default_title)
ret = False
for quality_type in movie['profile']['types']:
if not self.couldBeReleased(quality_type['quality']['identifier'] in pre_releases, release_dates):
if not self.couldBeReleased(quality_type['quality']['identifier'], release_dates, pre_releases):
log.info('Too early to search for %s, %s', (quality_type['quality']['identifier'], default_title))
continue
@@ -165,7 +146,7 @@ class Searcher(Plugin):
# See if better quality is available
for release in movie['releases']:
if release['quality']['order'] < quality_type['quality']['order'] and release['status_id'] not in [available_status.get('id'), ignored_status.get('id')]:
if release['quality']['order'] <= quality_type['quality']['order'] and release['status_id'] not in [available_status.get('id'), ignored_status.get('id')]:
has_better_quality += 1
# Don't search for quality lower then already available.
@@ -174,11 +155,7 @@ class Searcher(Plugin):
log.info('Search for %s in %s', (default_title, quality_type['quality']['label']))
quality = fireEvent('quality.single', identifier = quality_type['quality']['identifier'], single = True)
results = []
for search_type in search_types:
type_results = fireEvent('%s.search' % search_type, movie, quality, merge = True)
if type_results:
results += type_results
results = fireEvent('yarr.search', movie, quality, merge = True)
sorted_results = sorted(results, key = lambda k: k['score'], reverse = True)
if len(sorted_results) == 0:
@@ -195,13 +172,10 @@ class Searcher(Plugin):
# Add them to this movie releases list
for nzb in sorted_results:
nzb_identifier = md5(nzb['url'])
found_releases.append(nzb_identifier)
rls = db.query(Release).filter_by(identifier = nzb_identifier).first()
rls = db.query(Release).filter_by(identifier = md5(nzb['url'])).first()
if not rls:
rls = Release(
identifier = nzb_identifier,
identifier = md5(nzb['url']),
movie_id = movie.get('id'),
quality_id = quality_type.get('quality_id'),
status_id = available_status.get('id')
@@ -209,7 +183,6 @@ class Searcher(Plugin):
db.add(rls)
else:
[db.delete(old_info) for old_info in rls.info]
rls.last_edit = int(time.time())
db.commit()
@@ -250,12 +223,6 @@ class Searcher(Plugin):
break
elif downloaded != 'try_next':
break
# Remove releases that aren't found anymore
for release in movie.get('releases', []):
if release.get('status_id') == available_status.get('id') and release.get('identifier') not in found_releases:
fireEvent('release.delete', release.get('id'), single = True)
else:
log.info('Better quality (%s) already available or snatched for %s', (quality_type['quality']['label'], default_title))
fireEvent('movie.restatus', movie['id'])
@@ -271,92 +238,61 @@ class Searcher(Plugin):
def download(self, data, movie, manual = False):
# Test to see if any downloaders are enabled for this type
downloader_enabled = fireEvent('download.enabled', manual, data, single = True)
snatched_status = fireEvent('status.get', 'snatched', single = True)
if downloader_enabled:
# Download movie to temp
filedata = None
if data.get('download') and (ismethod(data.get('download')) or isfunction(data.get('download'))):
filedata = data.get('download')(url = data.get('url'), nzb_id = data.get('id'))
if filedata == 'try_next':
return filedata
snatched_status = fireEvent('status.get', 'snatched', single = True)
successful = fireEvent('download', data = data, movie = movie, manual = manual, filedata = filedata, single = True)
# Download movie to temp
filedata = None
if data.get('download') and (ismethod(data.get('download')) or isfunction(data.get('download'))):
filedata = data.get('download')(url = data.get('url'), nzb_id = data.get('id'))
if filedata == 'try_next':
return filedata
if successful:
successful = fireEvent('download', data = data, movie = movie, manual = manual, filedata = filedata, single = True)
try:
# Mark release as snatched
db = get_session()
rls = db.query(Release).filter_by(identifier = md5(data['url'])).first()
if rls:
rls.status_id = snatched_status.get('id')
db.commit()
if successful:
log_movie = '%s (%s) in %s' % (getTitle(movie['library']), movie['library']['year'], rls.quality.label)
snatch_message = 'Snatched "%s": %s' % (data.get('name'), log_movie)
log.info(snatch_message)
fireEvent('movie.snatched', message = snatch_message, data = rls.to_dict())
try:
# Mark release as snatched
db = get_session()
rls = db.query(Release).filter_by(identifier = md5(data['url'])).first()
if rls:
renamer_enabled = Env.setting('enabled', 'renamer')
# If renamer isn't used, mark movie done
if not Env.setting('enabled', 'renamer'):
active_status = fireEvent('status.get', 'active', single = True)
done_status = fireEvent('status.get', 'done', single = True)
try:
if movie['status_id'] == active_status.get('id'):
for profile_type in movie['profile']['types']:
if rls and profile_type['quality_id'] == rls.quality.id and profile_type['finish']:
log.info('Renamer disabled, marking movie as finished: %s', log_movie)
done_status = fireEvent('status.get', 'done', single = True)
rls.status_id = done_status.get('id') if not renamer_enabled else snatched_status.get('id')
db.commit()
# Mark release done
rls.status_id = done_status.get('id')
db.commit()
log_movie = '%s (%s) in %s' % (getTitle(movie['library']), movie['library']['year'], rls.quality.label)
snatch_message = 'Snatched "%s": %s' % (data.get('name'), log_movie)
log.info(snatch_message)
fireEvent('movie.snatched', message = snatch_message, data = rls.to_dict())
# Mark movie done
mvie = db.query(Movie).filter_by(id = movie['id']).first()
mvie.status_id = done_status.get('id')
db.commit()
except:
log.error('Failed marking movie finished, renamer disabled: %s', traceback.format_exc())
# If renamer isn't used, mark movie done
if not renamer_enabled:
active_status = fireEvent('status.get', 'active', single = True)
done_status = fireEvent('status.get', 'done', single = True)
try:
if movie['status_id'] == active_status.get('id'):
for profile_type in movie['profile']['types']:
if profile_type['quality_id'] == rls.quality.id and profile_type['finish']:
log.info('Renamer disabled, marking movie as finished: %s', log_movie)
except:
log.error('Failed marking movie finished: %s', traceback.format_exc())
# Mark release done
rls.status_id = done_status.get('id')
rls.last_edit = int(time.time())
db.commit()
# Mark movie done
mvie = db.query(Movie).filter_by(id = movie['id']).first()
mvie.status_id = done_status.get('id')
mvie.last_edit = int(time.time())
db.commit()
except:
log.error('Failed marking movie finished, renamer disabled: %s', traceback.format_exc())
except:
log.error('Failed marking movie finished: %s', traceback.format_exc())
return True
log.info('Tried to download, but none of the "%s" downloaders are enabled', (data.get('type', '')))
return True
log.info('Tried to download, but none of the downloaders are enabled')
return False
def getSearchTypes(self):
download_types = fireEvent('download.enabled_types', merge = True)
provider_types = fireEvent('provider.enabled_types', merge = True)
if download_types and len(list(set(provider_types) & set(download_types))) == 0:
log.error('There aren\'t any providers enabled for your downloader (%s). Check your settings.', ','.join(download_types))
raise NoProviders
for useless_provider in list(set(provider_types) - set(download_types)):
log.debug('Provider for "%s" enabled, but no downloader.', useless_provider)
search_types = download_types
if len(search_types) == 0:
log.error('There aren\'t any downloaders enabled. Please pick one in settings.')
raise NoDownloaders
return search_types
def correctMovie(self, nzb = {}, movie = {}, quality = {}, **kwargs):
imdb_results = kwargs.get('imdb_results', False)
@@ -533,7 +469,7 @@ class Searcher(Plugin):
return False
def couldBeReleased(self, is_pre_release, dates):
def couldBeReleased(self, wanted_quality, dates, pre_releases):
now = int(time.time())
@@ -545,7 +481,7 @@ class Searcher(Plugin):
if dates.get('theater', 0) < 0 or dates.get('dvd', 0) < 0:
return True
if is_pre_release:
if wanted_quality in pre_releases:
# Prerelease 1 week before theaters
if dates.get('theater') - 604800 < now:
return True
@@ -600,12 +536,3 @@ class Searcher(Plugin):
except:
log.error('Failed searching for next release: %s', traceback.format_exc())
return False
class SearchSetupError(Exception):
pass
class NoDownloaders(SearchSetupError):
pass
class NoProviders(SearchSetupError):
pass
-1
View File
@@ -23,7 +23,6 @@ class StatusPlugin(Plugin):
'deleted': 'Deleted',
'ignored': 'Ignored',
'available': 'Available',
'suggest': 'Suggest',
}
def __init__(self):
@@ -49,7 +49,6 @@ class Subtitle(Plugin):
available_languages = sum(group['subtitle_language'].itervalues(), [])
downloaded = []
files = [toUnicode(x) for x in group['files']['movie']]
log.debug('Searching for subtitles for: %s', files)
for lang in self.getLanguages():
if lang not in available_languages:
@@ -58,7 +57,6 @@ class Subtitle(Plugin):
downloaded.extend(download[subtitle])
for d_sub in downloaded:
log.info('Found subtitle (%s): %s', (d_sub.language.alpha2, files))
group['files']['subtitle'].add(d_sub.path)
group['subtitle_language'][d_sub.path] = [d_sub.language.alpha2]
+1 -17
View File
@@ -1,22 +1,6 @@
from couchpotato.api import addApiView
from couchpotato.core.event import fireEvent
from couchpotato.core.helpers.request import jsonified, getParam
from couchpotato.core.plugins.base import Plugin
class Suggestion(Plugin):
def __init__(self):
pass
addApiView('suggestion.view', self.getView)
def getView(self):
limit_offset = getParam('limit_offset', None)
total_movies, movies = fireEvent('movie.list', status = 'suggest', limit_offset = limit_offset, single = True)
return jsonified({
'success': True,
'empty': len(movies) == 0,
'total': total_movies,
'movies': movies,
})
@@ -1,13 +1,8 @@
.page.wizard .uniForm {
width: 80%;
margin: 0 auto 30px;
}
.page.wizard h1 {
padding: 10px 30px;
margin: 0;
display: block;
font-size: 30px;
font-size: 40px;
margin-top: 80px;
}
@@ -41,7 +41,8 @@ Page.Wizard = new Class({
},
'providers': {
'title': 'Are you registered at any of these sites?',
'description': 'CP uses these sites to search for movies. A few free are enabled by default, but it\'s always better to have a few more. Check settings for the full list of available providers.'
'description': 'CP uses these sites to search for movies. A few free are enabled by default, but it\'s always better to have a few more. Check settings for the full list of available providers.',
'include': ['nzb_providers', 'torrent_providers']
},
'renamer': {
'title': 'Move & rename the movies after downloading?',
@@ -82,7 +83,7 @@ Page.Wizard = new Class({
'target': self.el
},
'onComplete': function(){
window.location = App.createUrl('wanted');
window.location = App.createUrl();
}
});
}
@@ -212,6 +213,8 @@ Page.Wizard = new Class({
// Hide retention
self.el.getElement('.tab_searcher').hide();
self.el.getElement('.t_searcher').hide();
self.el.getElement('.t_nzb_providers').hide();
self.el.getElement('.t_torrent_providers').hide();
// Add pointer
new Element('.tab_wrapper').wraps(tabs).adopt(
@@ -1,21 +0,0 @@
config = {
'name': 'automation_providers',
'groups': [
{
'label': 'Watchlists',
'description': 'Check watchlists for new movies',
'type': 'list',
'name': 'watchlist_providers',
'tab': 'automation',
'options': [],
},
{
'label': 'Automated',
'description': 'Uses minimal requirements',
'type': 'list',
'name': 'automation_providers',
'tab': 'automation',
'options': [],
},
],
}
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'automation',
'list': 'automation_providers',
'name': 'bluray_automation',
'label': 'Blu-ray.com',
'description': 'Imports movies from blu-ray.com. (uses minimal requirements)',
@@ -1,28 +0,0 @@
from .main import Goodfilms
def start():
return Goodfilms()
config = [{
'name': 'goodfilms',
'groups': [
{
'tab': 'automation',
'list': 'watchlist_providers',
'name': 'goodfilms_automation',
'label': 'Goodfilms',
'description': 'import movies from your <a href="http://goodfil.ms">Goodfilms</a> queue',
'options': [
{
'name': 'automation_enabled',
'default': False,
'type': 'enabler',
},
{
'name': 'automation_username',
'label': 'Username',
},
],
},
],
}]
@@ -1,36 +0,0 @@
from couchpotato.core.logger import CPLog
from couchpotato.core.providers.automation.base import Automation
from bs4 import BeautifulSoup
log = CPLog(__name__)
class Goodfilms(Automation):
url = 'http://goodfil.ms/%s/queue'
def getIMDBids(self):
if not self.conf('automation_username'):
log.error('Please fill in your username')
return []
movies = []
for movie in self.getWatchlist():
imdb_id = self.search(movie.get('title'), movie.get('year'), imdb_only = True)
movies.append(imdb_id)
return movies
def getWatchlist(self):
url = self.url % self.conf('automation_username')
soup = BeautifulSoup(self.getHTMLData(url))
movies = []
for movie in soup.find_all('div', attrs = { 'class': 'movie', 'data-film-title': True }):
movies.append({ 'title': movie['data-film-title'], 'year': movie['data-film-year'] })
return movies
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'automation',
'list': 'watchlist_providers',
'name': 'imdb_automation',
'label': 'IMDB',
'description': 'From any <strong>public</strong> IMDB watchlists. Url should be the RSS link.',
@@ -27,7 +27,7 @@ class IMDB(Automation, RSS):
try:
rss_data = self.getHTMLData(url)
imdbs = getImdb(rss_data, multiple = True) if rss_data else []
imdbs = getImdb(rss_data, multiple = True)
for imdb in imdbs:
movies.append(imdb)
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'automation',
'list': 'automation_providers',
'name': 'itunes_automation',
'label': 'iTunes',
'description': 'From any <a href="http://itunes.apple.com/rss">iTunes</a> Store feed. Url should be the RSS link. (uses minimal requirements)',
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'automation',
'list': 'automation_providers',
'name': 'kinepolis_automation',
'label': 'Kinepolis',
'description': 'Imports movies from the current top 10 of kinepolis. (uses minimal requirements)',
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'automation',
'list': 'automation_providers',
'name': 'moviemeter_automation',
'label': 'Moviemeter',
'description': 'Imports movies from the current top 10 of moviemeter.nl. (uses minimal requirements)',
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'automation',
'list': 'watchlist_providers',
'name': 'moviesio',
'label': 'Movies.IO',
'description': 'Imports movies from <a href="http://movies.io">Movies.io</a> RSS watchlists',
@@ -1,29 +0,0 @@
from .main import Rottentomatoes
def start():
return Rottentomatoes()
config = [{
'name': 'rottentomatoes',
'groups': [
{
'tab': 'automation',
'list': 'automation_providers',
'name': 'rottentomatoes_automation',
'label': 'Rottentomatoes',
'description': 'Imports movies from the rottentomatoes "in theaters"-feed.',
'options': [
{
'name': 'automation_enabled',
'default': False,
'type': 'enabler',
},
{
'name': 'tomatometer_percent',
'default': '80',
'label': 'Tomatometer'
}
],
},
],
}]
@@ -1,48 +0,0 @@
from couchpotato.core.helpers.rss import RSS
from couchpotato.core.helpers.variable import tryInt
from couchpotato.core.logger import CPLog
from couchpotato.core.providers.automation.base import Automation
from xml.etree.ElementTree import QName
import datetime
import re
log = CPLog(__name__)
class Rottentomatoes(Automation, RSS):
interval = 1800
urls = {
'namespace': 'http://www.rottentomatoes.com/xmlns/rtmovie/',
'theater': 'http://www.rottentomatoes.com/syndication/rss/in_theaters.xml',
}
def getIMDBids(self):
movies = []
rss_movies = self.getRSSData(self.urls['theater'])
rating_tag = str(QName(self.urls['namespace'], 'tomatometer_percent'))
for movie in rss_movies:
value = self.getTextElement(movie, "title")
result = re.search('(?<=%\s).*', value)
if result:
log.info2('Something smells...')
rating = tryInt(self.getTextElement(movie, rating_tag))
name = result.group(0)
if rating < tryInt(self.conf('tomatometer_percent')):
log.info2('%s seems to be rotten...' % name)
else:
log.info2('Found %s fresh enough movies, enqueuing: %s' % (rating, name))
year = datetime.datetime.now().strftime("%Y")
imdb = self.search(name, year)
if imdb:
movies.append(imdb['imdb'])
return movies
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'automation',
'list': 'watchlist_providers',
'name': 'trakt_automation',
'label': 'Trakt',
'description': 'import movies from your own watchlist',
+5 -17
View File
@@ -46,8 +46,7 @@ class Provider(Plugin):
def getJsonData(self, url, **kwargs):
cache_key = '%s%s' % (md5(url), md5('%s' % kwargs.get('params', {})))
data = self.getCache(cache_key, url, **kwargs)
data = self.getCache(md5(url), url, **kwargs)
if data:
try:
@@ -57,24 +56,21 @@ class Provider(Plugin):
return []
def getRSSData(self, url, item_path = 'channel/item', **kwargs):
def getRSSData(self, url, **kwargs):
cache_key = '%s%s' % (md5(url), md5('%s' % kwargs.get('params', {})))
data = self.getCache(cache_key, url, **kwargs)
data = self.getCache(md5(url), url, **kwargs)
if data:
try:
data = XMLTree.fromstring(data)
return self.getElements(data, item_path)
return self.getElements(data, 'channel/item')
except:
log.error('Failed to parsing %s: %s', (self.getName(), traceback.format_exc()))
return []
def getHTMLData(self, url, **kwargs):
cache_key = '%s%s' % (md5(url), md5('%s' % kwargs.get('params', {})))
return self.getCache(cache_key, url, **kwargs)
return self.getCache(md5(url), url, **kwargs)
class YarrProvider(Provider):
@@ -88,16 +84,8 @@ class YarrProvider(Provider):
login_opener = None
def __init__(self):
addEvent('provider.enabled_types', self.getEnabledProviderType)
addEvent('provider.belongs_to', self.belongsTo)
addEvent('yarr.search', self.search)
addEvent('%s.search' % self.type, self.search)
def getEnabledProviderType(self):
if self.isEnabled():
return self.type
else:
return []
def login(self):
@@ -30,6 +30,11 @@ class MovieResultModifier(Plugin):
temp[imdb] = self.getLibraryTags(imdb)
order.append(imdb)
if item.get('via_imdb'):
if order.count(imdb):
order.remove(imdb)
order.insert(0, imdb)
# Merge dicts
temp[imdb] = mergeDicts(temp[imdb], item)
@@ -5,7 +5,9 @@ from couchpotato.core.helpers.request import jsonified, getParams
from couchpotato.core.logger import CPLog
from couchpotato.core.providers.movie.base import MovieProvider
from couchpotato.core.settings.model import Movie
from flask.helpers import json
import time
import traceback
log = CPLog(__name__)
@@ -15,9 +17,8 @@ class CouchPotatoApi(MovieProvider):
urls = {
'search': 'https://couchpota.to/api/search/%s/',
'info': 'https://couchpota.to/api/info/%s/',
'is_movie': 'https://couchpota.to/api/ismovie/%s/',
'eta': 'https://couchpota.to/api/eta/%s/',
'suggest': 'https://couchpota.to/api/suggest/',
'suggest': 'https://couchpota.to/api/suggest/%s/%s/',
}
http_time_between_calls = 0
api_version = 1
@@ -28,47 +29,58 @@ class CouchPotatoApi(MovieProvider):
addEvent('movie.info', self.getInfo, priority = 1)
addEvent('movie.search', self.search, priority = 1)
addEvent('movie.release_date', self.getReleaseDate)
addEvent('movie.suggest', self.suggest)
addEvent('movie.is_movie', self.isMovie)
def search(self, q, limit = 12):
return self.getJsonData(self.urls['search'] % tryUrlencode(q), headers = self.getRequestHeaders())
def isMovie(self, identifier = None):
cache_key = 'cpapi.cache.%s' % q
cached = self.getCache(cache_key, self.urls['search'] % tryUrlencode(q), timeout = 3, headers = self.getRequestHeaders())
if not identifier:
return
if cached:
try:
movies = json.loads(cached)
return movies
except:
log.error('Failed parsing search results: %s', traceback.format_exc())
data = self.getJsonData(self.urls['is_movie'] % identifier, headers = self.getRequestHeaders())
if data:
return data.get('is_movie', True)
return True
return []
def getInfo(self, identifier = None):
if not identifier:
return
result = self.getJsonData(self.urls['info'] % identifier, headers = self.getRequestHeaders())
if result: return result
cache_key = 'cpapi.cache.info.%s' % identifier
cached = self.getCache(cache_key, self.urls['info'] % identifier, timeout = 3, headers = self.getRequestHeaders())
if cached:
try:
movie = json.loads(cached)
return movie
except:
log.error('Failed parsing info results: %s', traceback.format_exc())
return {}
def getReleaseDate(self, identifier = None):
if identifier is None: return {}
try:
data = self.urlopen(self.urls['eta'] % identifier, headers = self.getRequestHeaders())
dates = json.loads(data)
log.debug('Found ETA for %s: %s', (identifier, dates))
return dates
except Exception, e:
log.error('Error getting ETA for %s: %s', (identifier, e))
dates = self.getJsonData(self.urls['eta'] % identifier, headers = self.getRequestHeaders())
log.debug('Found ETA for %s: %s', (identifier, dates))
return dates
return {}
def suggest(self, movies = [], ignore = []):
suggestions = self.getJsonData(self.urls['suggest'], params = {
'movies': ','.join(movies),
#'ignore': ','.join(ignore),
})
log.info('Found Suggestions for %s', (suggestions))
try:
data = self.urlopen(self.urls['suggest'] % (','.join(movies), ','.join(ignore)))
suggestions = json.loads(data)
log.info('Found Suggestions for %s', (suggestions))
except Exception, e:
log.error('Error getting suggestions for %s: %s', (movies, e))
return suggestions
@@ -1,15 +0,0 @@
config = {
'name': 'nzb_providers',
'groups': [
{
'label': 'Usenet',
'description': 'Providers searching usenet for new releases',
'wizard': True,
'type': 'list',
'name': 'nzb_providers',
'tab': 'searcher',
'subtab': 'providers',
'options': [],
},
],
}
@@ -8,11 +8,9 @@ config = [{
'groups': [
{
'tab': 'searcher',
'subtab': 'providers',
'list': 'nzb_providers',
'subtab': 'nzb_providers',
'name': 'binsearch',
'description': 'Free provider, less accurate. See <a href="https://www.binsearch.info/">BinSearch</a>',
'wizard': True,
'options': [
{
'name': 'enabled',
@@ -22,10 +22,11 @@ class BinSearch(NZBProvider):
def _search(self, movie, quality, results):
q = '%s %s' % (movie['library']['identifier'], quality.get('identifier'))
arguments = tryUrlencode({
'q': movie['library']['identifier'],
'q': q,
'm': 'n',
'max': 400,
'max': 250,
'adv_age': Env.setting('retention', 'nzb'),
'adv_sort': 'date',
'adv_col': 'on',
@@ -8,11 +8,9 @@ config = [{
'groups': [
{
'tab': 'searcher',
'subtab': 'providers',
'list': 'nzb_providers',
'subtab': 'nzb_providers',
'name': 'FTDWorld',
'description': 'Free provider, less accurate. See <a href="http://ftdworld.net">FTDWorld</a>',
'wizard': True,
'options': [
{
'name': 'enabled',
@@ -4,7 +4,6 @@ from couchpotato.core.logger import CPLog
from couchpotato.core.providers.nzb.base import NZBProvider
from couchpotato.environment import Env
from dateutil.parser import parse
import json
import traceback
log = CPLog(__name__)
@@ -16,7 +15,7 @@ class FTDWorld(NZBProvider):
'search': 'http://ftdworld.net/api/index.php?%s',
'detail': 'http://ftdworld.net/spotinfo.php?id=%s',
'download': 'http://ftdworld.net/cgi-bin/nzbdown.pl?fileID=%s',
'login': 'http://ftdworld.net/api/login.php',
'login': 'http://ftdworld.net/index.php',
}
http_time_between_calls = 3 #seconds
@@ -57,7 +56,6 @@ class FTDWorld(NZBProvider):
'id': nzb_id,
'name': toUnicode(item.get('Title')),
'age': self.calculateAge(tryInt(item.get('Created'))),
'size': item.get('Size', 0),
'url': self.urls['download'] % nzb_id,
'download': self.loginDownload,
'detail_url': self.urls['detail'] % nzb_id,
@@ -75,7 +73,4 @@ class FTDWorld(NZBProvider):
})
def loginSuccess(self, output):
try:
return json.loads(output).get('goodToGo', False)
except:
return False
return 'password is incorrect' not in output
@@ -8,8 +8,7 @@ config = [{
'groups': [
{
'tab': 'searcher',
'subtab': 'providers',
'list': 'nzb_providers',
'subtab': 'nzb_providers',
'name': 'newznab',
'order': 10,
'description': 'Enable <a href="http://newznab.com/" target="_blank">NewzNab providers</a> such as <a href="https://nzb.su" target="_blank">NZB.su</a>, \
+3 -14
View File
@@ -69,7 +69,7 @@ class Newznab(NZBProvider, RSS):
results.append({
'id': nzb_id,
'provider_extra': urlparse(host['host']).hostname or host['host'],
'provider_extra': host['host'],
'name': self.getTextElement(nzb, 'title'),
'age': self.calculateAge(int(time.mktime(parse(date).timetuple()))),
'size': int(self.getElement(nzb, 'enclosure').attrib['length']) / 1024 / 1024,
@@ -104,23 +104,12 @@ class Newznab(NZBProvider, RSS):
return result
def getUrl(self, host, type):
if '?page=newznabapi' in host:
return cleanHost(host)[:-1] + '&t=' + type
return cleanHost(host) + 'api?t=' + type
def isDisabled(self, host = None):
def isDisabled(self, host):
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
def isEnabled(self, host):
return NZBProvider.isEnabled(self) and host['host'] and host['api_key'] and int(host['use'])
def getApiExt(self, host):
@@ -8,11 +8,9 @@ config = [{
'groups': [
{
'tab': 'searcher',
'subtab': 'providers',
'list': 'nzb_providers',
'subtab': 'nzb_providers',
'name': 'NZBClub',
'description': 'Free provider, less accurate. See <a href="https://www.nzbclub.com/">NZBClub</a>',
'wizard': True,
'options': [
{
'name': 'enabled',

Some files were not shown because too many files have changed in this diff Show More