Merge branch 'refs/heads/develop'
This commit is contained in:
@@ -136,6 +136,7 @@ if __name__ == '__main__':
|
||||
except socket.error as e:
|
||||
# log when socket receives SIGINT, but continue.
|
||||
# previous code would have skipped over other types of IO errors too.
|
||||
nr, msg = e
|
||||
if nr != 4:
|
||||
try:
|
||||
l.log.critical(traceback.format_exc())
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from axl.axel import Event
|
||||
from couchpotato.core.helpers.variable import mergeDicts, natcmp
|
||||
from couchpotato.core.helpers.variable import mergeDicts, natsortKey
|
||||
from couchpotato.core.logger import CPLog
|
||||
import threading
|
||||
import traceback
|
||||
@@ -51,11 +51,6 @@ def addEvent(name, handler, priority = 100):
|
||||
})
|
||||
|
||||
|
||||
def removeEvent(name, handler):
|
||||
e = events[name]
|
||||
e -= handler
|
||||
|
||||
|
||||
def fireEvent(name, *args, **kwargs):
|
||||
if name not in events: return
|
||||
|
||||
@@ -106,7 +101,7 @@ def fireEvent(name, *args, **kwargs):
|
||||
result = e(*args, **kwargs)
|
||||
|
||||
result_keys = result.keys()
|
||||
result_keys.sort(natcmp)
|
||||
result_keys.sort(key = natsortKey)
|
||||
|
||||
if options['single'] and not options['merge']:
|
||||
results = None
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from couchpotato.core.helpers.encoding import toUnicode
|
||||
from couchpotato.core.helpers.variable import natcmp
|
||||
from couchpotato.core.helpers.variable import natsortKey
|
||||
from urllib import unquote
|
||||
import re
|
||||
|
||||
@@ -8,8 +8,13 @@ def getParams(params):
|
||||
|
||||
reg = re.compile('^[a-z0-9_\.]+$')
|
||||
|
||||
# Sort keys
|
||||
param_keys = params.keys()
|
||||
param_keys.sort(key = natsortKey)
|
||||
|
||||
temp = {}
|
||||
for param, value in sorted(params.items()):
|
||||
for param in param_keys:
|
||||
value = params[param]
|
||||
|
||||
nest = re.split("([\[\]]+)", param)
|
||||
if len(nest) > 1:
|
||||
|
||||
@@ -214,16 +214,9 @@ def tryFloat(s):
|
||||
return float(s)
|
||||
except: return 0
|
||||
|
||||
|
||||
def natsortKey(s):
|
||||
return map(tryInt, re.findall(r'(\d+|\D+)', s))
|
||||
|
||||
|
||||
def natcmp(a, b):
|
||||
a2 = natsortKey(a)
|
||||
b2 = natsortKey(b)
|
||||
|
||||
return (a2 > b2) - (a2 < b2)
|
||||
def natsortKey(string_):
|
||||
"""See http://www.codinghorror.com/blog/archives/001018.html"""
|
||||
return [int(s) if s.isdigit() else s for s in re.split(r'(\d+)', string_)]
|
||||
|
||||
|
||||
def toIterable(value):
|
||||
@@ -295,3 +288,14 @@ def dictIsSubset(a, b):
|
||||
def isSubFolder(sub_folder, base_folder):
|
||||
# Returns True if sub_folder is the same as or inside base_folder
|
||||
return base_folder and sub_folder and ss(os.path.normpath(base_folder).rstrip(os.path.sep) + os.path.sep) in ss(os.path.normpath(sub_folder).rstrip(os.path.sep) + os.path.sep)
|
||||
|
||||
# From SABNZBD
|
||||
re_password = [re.compile(r'([^/\\]+)[/\\](.+)'), re.compile(r'(.+){{([^{}]+)}}$'), re.compile(r'(.+)\s+password\s*=\s*(.+)$', re.I)]
|
||||
def scanForPassword(name):
|
||||
m = None
|
||||
for reg in re_password:
|
||||
m = reg.search(name)
|
||||
if m: break
|
||||
|
||||
if m:
|
||||
return m.group(1).strip('. '), m.group(2).strip()
|
||||
|
||||
@@ -282,13 +282,15 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
|
||||
|
||||
now = int(time.time())
|
||||
now_year = date.today().year
|
||||
now_month = date.today().month
|
||||
|
||||
if (year is None or year < now_year - 1) and (not dates or (dates.get('theater', 0) == 0 and dates.get('dvd', 0) == 0)):
|
||||
return True
|
||||
else:
|
||||
|
||||
# Don't allow movies with years to far in the future
|
||||
if year is not None and year > now_year + 1:
|
||||
add_year = 1 if now_month > 10 else 0 # Only allow +1 year if end of the year
|
||||
if year is not None and year > (now_year + add_year):
|
||||
return False
|
||||
|
||||
# For movies before 1972
|
||||
|
||||
34
couchpotato/core/notifications/boxcar2/__init__.py
Normal file
34
couchpotato/core/notifications/boxcar2/__init__.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from .main import Boxcar2
|
||||
|
||||
|
||||
def start():
|
||||
return Boxcar2()
|
||||
|
||||
config = [{
|
||||
'name': 'boxcar2',
|
||||
'groups': [
|
||||
{
|
||||
'tab': 'notifications',
|
||||
'list': 'notification_providers',
|
||||
'name': 'boxcar2',
|
||||
'options': [
|
||||
{
|
||||
'name': 'enabled',
|
||||
'default': 0,
|
||||
'type': 'enabler',
|
||||
},
|
||||
{
|
||||
'name': 'token',
|
||||
'description': ('Your Boxcar access token.', 'Can be found in the app under settings')
|
||||
},
|
||||
{
|
||||
'name': 'on_snatch',
|
||||
'default': 0,
|
||||
'type': 'bool',
|
||||
'advanced': True,
|
||||
'description': 'Also send message when movie is snatched.',
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
}]
|
||||
39
couchpotato/core/notifications/boxcar2/main.py
Normal file
39
couchpotato/core/notifications/boxcar2/main.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from couchpotato.core.helpers.encoding import toUnicode
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.notifications.base import Notification
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
class Boxcar2(Notification):
|
||||
|
||||
url = 'https://new.boxcar.io/api/notifications'
|
||||
|
||||
def notify(self, message = '', data = None, listener = None):
|
||||
if not data: data = {}
|
||||
|
||||
try:
|
||||
message = message.strip()
|
||||
|
||||
long_message = ''
|
||||
if listener == 'test':
|
||||
long_message = 'This is a test message'
|
||||
elif data.get('identifier'):
|
||||
long_message = 'More movie info <a href="http://www.imdb.com/title/%s/">on IMDB</a>' % data['identifier']
|
||||
|
||||
data = {
|
||||
'user_credentials': self.conf('token'),
|
||||
'notification[title]': toUnicode(message),
|
||||
'notification[long_message]': toUnicode(long_message),
|
||||
}
|
||||
|
||||
self.urlopen(self.url, data = data)
|
||||
except:
|
||||
log.error('Make sure the token provided is for the correct device')
|
||||
return False
|
||||
|
||||
log.info('Boxcar notification successful.')
|
||||
return True
|
||||
|
||||
def isEnabled(self):
|
||||
return super(Boxcar2, self).isEnabled() and self.conf('token')
|
||||
@@ -1,7 +1,7 @@
|
||||
from couchpotato.core.event import fireEvent, addEvent
|
||||
from couchpotato.core.helpers.encoding import ss, toSafeString, \
|
||||
toUnicode, sp
|
||||
from couchpotato.core.helpers.variable import getExt, md5, isLocalIP
|
||||
from couchpotato.core.helpers.variable import getExt, md5, isLocalIP, scanForPassword, tryInt
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.environment import Env
|
||||
import requests
|
||||
@@ -283,8 +283,17 @@ class Plugin(object):
|
||||
return value
|
||||
|
||||
def createNzbName(self, data, media):
|
||||
release_name = data.get('name')
|
||||
tag = self.cpTag(media)
|
||||
return '%s%s' % (toSafeString(toUnicode(data.get('name'))[:127 - len(tag)]), tag)
|
||||
|
||||
# Check if password is filename
|
||||
name_password = scanForPassword(data.get('name'))
|
||||
if name_password:
|
||||
release_name, password = name_password
|
||||
tag += '{{%s}}' % password
|
||||
|
||||
max_length = 127 - len(tag) # Some filesystems don't support 128+ long filenames
|
||||
return '%s%s' % (toSafeString(toUnicode(release_name)[:max_length]), tag)
|
||||
|
||||
def createFileName(self, data, filedata, media):
|
||||
name = sp(os.path.join(self.createNzbName(data, media)))
|
||||
@@ -298,6 +307,43 @@ class Plugin(object):
|
||||
|
||||
return ''
|
||||
|
||||
def checkFilesChanged(self, files, unchanged_for = 60):
|
||||
now = time.time()
|
||||
file_too_new = False
|
||||
|
||||
for cur_file in files:
|
||||
|
||||
# File got removed while checking
|
||||
if not os.path.isfile(cur_file):
|
||||
file_too_new = now
|
||||
break
|
||||
|
||||
# File has changed in last 60 seconds
|
||||
file_time = self.getFileTimes(cur_file)
|
||||
for t in file_time:
|
||||
if t > now - unchanged_for:
|
||||
file_too_new = tryInt(time.time() - t)
|
||||
break
|
||||
|
||||
if file_too_new:
|
||||
break
|
||||
|
||||
if file_too_new:
|
||||
try:
|
||||
time_string = time.ctime(file_time[0])
|
||||
except:
|
||||
try:
|
||||
time_string = time.ctime(file_time[1])
|
||||
except:
|
||||
time_string = 'unknown'
|
||||
|
||||
return file_too_new, time_string
|
||||
|
||||
return False, None
|
||||
|
||||
def getFileTimes(self, file_path):
|
||||
return [os.path.getmtime(file_path), os.path.getctime(file_path) if os.name != 'posix' else 0]
|
||||
|
||||
def isDisabled(self):
|
||||
return not self.isEnabled()
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from datetime import date
|
||||
from couchpotato import get_session
|
||||
from couchpotato.api import addApiView
|
||||
from couchpotato.core.event import fireEvent
|
||||
@@ -65,6 +66,7 @@ class Dashboard(Plugin):
|
||||
|
||||
active = q.all()
|
||||
movies = []
|
||||
now_year = date.today().year
|
||||
|
||||
if len(active) > 0:
|
||||
|
||||
@@ -91,8 +93,8 @@ class Dashboard(Plugin):
|
||||
if coming_soon:
|
||||
|
||||
# Don't list older movies
|
||||
if ((not late and (not eta.get('dvd') and not eta.get('theater') or eta.get('dvd') and eta.get('dvd') > (now - 2419200))) or
|
||||
(late and (eta.get('dvd', 0) > 0 or eta.get('theater')) and eta.get('dvd') < (now - 2419200))):
|
||||
if ((not late and (year >= now_year-1) and (not eta.get('dvd') and not eta.get('theater') or eta.get('dvd') and eta.get('dvd') > (now - 2419200))) or
|
||||
(late and ((year < now_year-1) or ((eta.get('dvd', 0) > 0 or eta.get('theater')) and eta.get('dvd') < (now - 2419200))))):
|
||||
movie_ids.append(movie_id)
|
||||
|
||||
if len(movie_ids) >= limit:
|
||||
|
||||
@@ -736,8 +736,15 @@ Remove it if you want it to be renamed (again, or at least let it try again)
|
||||
def moveFile(self, old, dest, forcemove = False):
|
||||
dest = ss(dest)
|
||||
try:
|
||||
if forcemove:
|
||||
shutil.move(old, dest)
|
||||
if forcemove or self.conf('file_action') not in ['copy', 'link']:
|
||||
try:
|
||||
shutil.move(old, dest)
|
||||
except:
|
||||
if os.path.exists(dest):
|
||||
log.error('Successfully moved file "%s", but something went wrong: %s', (dest, traceback.format_exc()))
|
||||
os.unlink(old)
|
||||
else:
|
||||
raise
|
||||
elif self.conf('file_action') == 'copy':
|
||||
shutil.copy(old, dest)
|
||||
elif self.conf('file_action') == 'link':
|
||||
@@ -755,8 +762,6 @@ Remove it if you want it to be renamed (again, or at least let it try again)
|
||||
os.rename(old + '.link', old)
|
||||
except:
|
||||
log.error('Couldn\'t symlink file "%s" to "%s". Copied instead. Error: %s. ', (old, dest, traceback.format_exc()))
|
||||
else:
|
||||
shutil.move(old, dest)
|
||||
|
||||
try:
|
||||
os.chmod(dest, Env.getPermission('file'))
|
||||
@@ -764,15 +769,6 @@ Remove it if you want it to be renamed (again, or at least let it try again)
|
||||
os.popen('icacls "' + dest + '"* /reset /T')
|
||||
except:
|
||||
log.error('Failed setting permissions for file: %s, %s', (dest, traceback.format_exc(1)))
|
||||
|
||||
except OSError as err:
|
||||
# Copying from a filesystem with octal permission to an NTFS file system causes a permission error. In this case ignore it.
|
||||
if not hasattr(os, 'chmod') or err.errno != errno.EPERM:
|
||||
raise
|
||||
else:
|
||||
if os.path.exists(dest):
|
||||
os.unlink(old)
|
||||
|
||||
except:
|
||||
log.error('Couldn\'t move file "%s" to "%s": %s', (old, dest, traceback.format_exc()))
|
||||
raise
|
||||
@@ -1127,29 +1123,9 @@ Remove it if you want it to be renamed (again, or at least let it try again)
|
||||
|
||||
# Check if archive is fresh and maybe still copying/moving/downloading, ignore files newer than 1 minute
|
||||
if check_file_date:
|
||||
file_too_new = False
|
||||
for cur_file in archive['files']:
|
||||
if not os.path.isfile(cur_file):
|
||||
file_too_new = time.time()
|
||||
break
|
||||
file_time = [os.path.getmtime(cur_file), os.path.getctime(cur_file)]
|
||||
for t in file_time:
|
||||
if t > time.time() - 60:
|
||||
file_too_new = tryInt(time.time() - t)
|
||||
break
|
||||
|
||||
if file_too_new:
|
||||
break
|
||||
|
||||
if file_too_new:
|
||||
try:
|
||||
time_string = time.ctime(file_time[0])
|
||||
except:
|
||||
try:
|
||||
time_string = time.ctime(file_time[1])
|
||||
except:
|
||||
time_string = 'unknown'
|
||||
files_too_new, time_string = self.checkFilesChanged(archive['files'])
|
||||
|
||||
if files_too_new:
|
||||
log.info('Archive seems to be still copying/moving/downloading or just copied/moved/downloaded (created on %s), ignoring for now: %s', (time_string, os.path.basename(archive['file'])))
|
||||
continue
|
||||
|
||||
|
||||
@@ -291,41 +291,21 @@ class Scanner(Plugin):
|
||||
break
|
||||
|
||||
# Check if movie is fresh and maybe still unpacking, ignore files newer than 1 minute
|
||||
file_too_new = False
|
||||
for cur_file in group['unsorted_files']:
|
||||
if not os.path.isfile(cur_file):
|
||||
file_too_new = time.time()
|
||||
break
|
||||
file_time = [os.path.getmtime(cur_file), os.path.getctime(cur_file)]
|
||||
for t in file_time:
|
||||
if t > time.time() - 60:
|
||||
file_too_new = tryInt(time.time() - t)
|
||||
break
|
||||
if check_file_date:
|
||||
files_too_new, time_string = self.checkFilesChanged(group['unsorted_files'])
|
||||
if files_too_new:
|
||||
log.info('Files seem to be still unpacking or just unpacked (created on %s), ignoring for now: %s', (time_string, identifier))
|
||||
|
||||
if file_too_new:
|
||||
break
|
||||
# Delete the unsorted list
|
||||
del group['unsorted_files']
|
||||
|
||||
if check_file_date and file_too_new:
|
||||
try:
|
||||
time_string = time.ctime(file_time[0])
|
||||
except:
|
||||
try:
|
||||
time_string = time.ctime(file_time[1])
|
||||
except:
|
||||
time_string = 'unknown'
|
||||
|
||||
log.info('Files seem to be still unpacking or just unpacked (created on %s), ignoring for now: %s', (time_string, identifier))
|
||||
|
||||
# Delete the unsorted list
|
||||
del group['unsorted_files']
|
||||
|
||||
continue
|
||||
continue
|
||||
|
||||
# Only process movies newer than x
|
||||
if newer_than and newer_than > 0:
|
||||
has_new_files = False
|
||||
for cur_file in group['unsorted_files']:
|
||||
file_time = [os.path.getmtime(cur_file), os.path.getctime(cur_file)]
|
||||
file_time = self.getFileTimes(cur_file)
|
||||
if file_time[0] > newer_than or file_time[1] > newer_than:
|
||||
has_new_files = True
|
||||
break
|
||||
|
||||
@@ -22,8 +22,11 @@ class NZBClub(NZBProvider, RSS):
|
||||
|
||||
q = '"%s %s"' % (title, movie['library']['year'])
|
||||
|
||||
params = tryUrlencode({
|
||||
q_param = tryUrlencode({
|
||||
'q': q,
|
||||
})
|
||||
|
||||
params = tryUrlencode({
|
||||
'ig': 1,
|
||||
'rpp': 200,
|
||||
'st': 5,
|
||||
@@ -31,7 +34,7 @@ class NZBClub(NZBProvider, RSS):
|
||||
'ns': 1,
|
||||
})
|
||||
|
||||
nzbs = self.getRSSData(self.urls['search'] % params)
|
||||
nzbs = self.getRSSData(self.urls['search'] % ('%s&%s' % (q_param, params)))
|
||||
|
||||
for nzb in nzbs:
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ class ThePirateBay(TorrentMagnetProvider):
|
||||
'http://nl.tpb.li',
|
||||
'http://proxybay.eu',
|
||||
'https://www.getpirate.com',
|
||||
'http://pirateproxy.ca',
|
||||
'http://piratebay.io',
|
||||
]
|
||||
|
||||
def _searchOnTitle(self, title, movie, quality, results):
|
||||
|
||||
7
couchpotato/core/providers/userscript/reddit/__init__.py
Normal file
7
couchpotato/core/providers/userscript/reddit/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from .main import Reddit
|
||||
|
||||
|
||||
def start():
|
||||
return Reddit()
|
||||
|
||||
config = []
|
||||
17
couchpotato/core/providers/userscript/reddit/main.py
Normal file
17
couchpotato/core/providers/userscript/reddit/main.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from couchpotato import fireEvent
|
||||
from couchpotato.core.helpers.variable import splitString
|
||||
from couchpotato.core.providers.userscript.base import UserscriptBase
|
||||
|
||||
|
||||
class Reddit(UserscriptBase):
|
||||
|
||||
includes = ['*://www.reddit.com/r/Ijustwatched/comments/*']
|
||||
|
||||
def getMovie(self, url):
|
||||
name = splitString(url, '/')[-1]
|
||||
if name.startswith('ijw_'):
|
||||
name = name[4:]
|
||||
|
||||
year_name = fireEvent('scanner.name_year', name, single = True)
|
||||
|
||||
return self.search(year_name.get('name'), year_name.get('year'))
|
||||
@@ -11,7 +11,7 @@
|
||||
# Source: http://pypi.python.org/pypi/axel
|
||||
# Docs: http://packages.python.org/axel
|
||||
|
||||
from couchpotato.core.helpers.variable import natcmp
|
||||
from couchpotato.core.helpers.variable import natsortKey
|
||||
import Queue
|
||||
import hashlib
|
||||
import sys
|
||||
@@ -158,7 +158,10 @@ class Event(object):
|
||||
t.daemon = True
|
||||
t.start()
|
||||
|
||||
for handler in sorted(self.handlers.iterkeys(), cmp = natcmp):
|
||||
handler_keys = self.handlers.keys()
|
||||
handler_keys.sort(key = natsortKey)
|
||||
|
||||
for handler in handler_keys:
|
||||
self.queue.put(handler)
|
||||
|
||||
if self.asynchronous:
|
||||
|
||||
Reference in New Issue
Block a user