Merge branch 'develop'

This commit is contained in:
Ruud
2014-09-23 14:54:21 +02:00
19 changed files with 290 additions and 90 deletions
+2 -2
View File
@@ -286,13 +286,13 @@ config = [{
'name': 'permission_folder', 'name': 'permission_folder',
'default': '0755', 'default': '0755',
'label': 'Folder CHMOD', 'label': 'Folder CHMOD',
'description': 'Can be either decimal (493) or octal (leading zero: 0755)', 'description': 'Can be either decimal (493) or octal (leading zero: 0755). <a target="_blank" href="http://permissions-calculator.org/">Calculate the correct value</a>',
}, },
{ {
'name': 'permission_file', 'name': 'permission_file',
'default': '0755', 'default': '0755',
'label': 'File CHMOD', 'label': 'File CHMOD',
'description': 'Same as Folder CHMOD but for files', 'description': 'See Folder CHMOD description, but for files',
}, },
], ],
}, },
+5 -5
View File
@@ -25,7 +25,7 @@ class Transmission(DownloaderBase):
def connect(self): def connect(self):
# Load host from config and split out port. # Load host from config and split out port.
host = cleanHost(self.conf('host'), protocol = False).split(':') host = cleanHost(self.conf('host')).rstrip('/').rsplit(':', 1)
if not isInt(host[1]): if not isInt(host[1]):
log.error('Config properties are not filled in correctly, port is missing.') log.error('Config properties are not filled in correctly, port is missing.')
return False return False
@@ -162,11 +162,11 @@ class Transmission(DownloaderBase):
class TransmissionRPC(object): class TransmissionRPC(object):
"""TransmissionRPC lite library""" """TransmissionRPC lite library"""
def __init__(self, host = 'localhost', port = 9091, rpc_url = 'transmission', username = None, password = None): def __init__(self, host = 'http://localhost', port = 9091, rpc_url = 'transmission', username = None, password = None):
super(TransmissionRPC, self).__init__() super(TransmissionRPC, self).__init__()
self.url = 'http://' + host + ':' + str(port) + '/' + rpc_url + '/rpc' self.url = host + ':' + str(port) + '/' + rpc_url + '/rpc'
self.tag = 0 self.tag = 0
self.session_id = 0 self.session_id = 0
self.session = {} self.session = {}
@@ -274,8 +274,8 @@ config = [{
}, },
{ {
'name': 'host', 'name': 'host',
'default': 'localhost:9091', 'default': 'http://localhost:9091',
'description': 'Hostname with port. Usually <strong>localhost:9091</strong>', 'description': 'Hostname with port. Usually <strong>http://localhost:9091</strong>',
}, },
{ {
'name': 'rpc_url', 'name': 'rpc_url',
+22
View File
@@ -382,6 +382,28 @@ def getFreeSpace(directories):
return free_space return free_space
def getSize(paths):
single = not isinstance(paths, (tuple, list))
if single:
paths = [paths]
total_size = 0
for path in paths:
path = sp(path)
if os.path.isdir(path):
total_size = 0
for dirpath, _, filenames in os.walk(path):
for f in filenames:
total_size += os.path.getsize(sp(os.path.join(dirpath, f)))
elif os.path.isfile(path):
total_size += os.path.getsize(path)
return total_size / 1048576 # MB
def find(func, iterable): def find(func, iterable):
for item in iterable: for item in iterable:
if func(item): if func(item):
@@ -42,6 +42,7 @@ class Base(TorrentProvider):
link = result.find('span', attrs = {'class': 'torrent_name_link'}).parent link = result.find('span', attrs = {'class': 'torrent_name_link'}).parent
url = result.find('td', attrs = {'class': 'torrent_td'}).find('a') url = result.find('td', attrs = {'class': 'torrent_td'}).find('a')
tds = result.find_all('td')
results.append({ results.append({
'id': link['href'].replace('torrents.php?torrentid=', ''), 'id': link['href'].replace('torrents.php?torrentid=', ''),
@@ -49,8 +50,8 @@ class Base(TorrentProvider):
'url': self.urls['download'] % url['href'], 'url': self.urls['download'] % url['href'],
'detail_url': self.urls['download'] % link['href'], 'detail_url': self.urls['download'] % link['href'],
'size': self.parseSize(result.find_all('td')[5].string), 'size': self.parseSize(result.find_all('td')[5].string),
'seeders': tryInt(result.find_all('td')[7].string), 'seeders': tryInt(tds[len(tds)-2].string),
'leechers': tryInt(result.find_all('td')[8].string), 'leechers': tryInt(tds[len(tds)-1].string),
}) })
except: except:
@@ -73,4 +73,24 @@ config = [{
], ],
}, },
], ],
}, {
'name': 'torrent',
'groups': [
{
'tab': 'searcher',
'name': 'searcher',
'wizard': True,
'options': [
{
'name': 'minimum_seeders',
'advanced': True,
'label': 'Minimum seeders',
'description': 'Ignore torrents with seeders below this number',
'default': 1,
'type': 'int',
'unit': 'seeders'
},
],
},
],
}] }]
+5 -1
View File
@@ -27,6 +27,10 @@ class MovieBase(MovieTypeBase):
addApiView('movie.add', self.addView, docs = { addApiView('movie.add', self.addView, docs = {
'desc': 'Add new movie to the wanted list', 'desc': 'Add new movie to the wanted list',
'return': {'type': 'object', 'example': """{
'success': True,
'movie': object
}"""},
'params': { 'params': {
'identifier': {'desc': 'IMDB id of the movie your want to add.'}, 'identifier': {'desc': 'IMDB id of the movie your want to add.'},
'profile_id': {'desc': 'ID of quality profile you want the add the movie in. If empty will use the default profile.'}, 'profile_id': {'desc': 'ID of quality profile you want the add the movie in. If empty will use the default profile.'},
@@ -150,7 +154,7 @@ class MovieBase(MovieTypeBase):
for release in fireEvent('release.for_media', m['_id'], single = True): for release in fireEvent('release.for_media', m['_id'], single = True):
if release.get('status') in ['downloaded', 'snatched', 'seeding', 'done']: if release.get('status') in ['downloaded', 'snatched', 'seeding', 'done']:
if params.get('ignore_previous', False): if params.get('ignore_previous', False):
fireEvent('release.update_status', m['_id'], status = 'ignored') fireEvent('release.update_status', release['_id'], status = 'ignored')
else: else:
fireEvent('release.delete', release['_id'], single = True) fireEvent('release.delete', release['_id'], single = True)
@@ -2,7 +2,7 @@ import base64
import time import time
from couchpotato.core.event import addEvent, fireEvent from couchpotato.core.event import addEvent, fireEvent
from couchpotato.core.helpers.encoding import tryUrlencode from couchpotato.core.helpers.encoding import tryUrlencode, ss
from couchpotato.core.logger import CPLog from couchpotato.core.logger import CPLog
from couchpotato.core.media.movie.providers.base import MovieProvider from couchpotato.core.media.movie.providers.base import MovieProvider
from couchpotato.environment import Env from couchpotato.environment import Env
@@ -66,7 +66,7 @@ class CouchPotatoApi(MovieProvider):
if not name: if not name:
return return
name_enc = base64.b64encode(name) name_enc = base64.b64encode(ss(name))
return self.getJsonData(self.urls['validate'] % name_enc, headers = self.getRequestHeaders()) return self.getJsonData(self.urls['validate'] % name_enc, headers = self.getRequestHeaders())
def isMovie(self, identifier = None): def isMovie(self, identifier = None):
@@ -25,6 +25,6 @@ class Filmstarts(UserscriptBase):
name = html.find("meta", {"property":"og:title"})['content'] name = html.find("meta", {"property":"og:title"})['content']
# Year of production is not available in the meta data, so get it from the table # Year of production is not available in the meta data, so get it from the table
year = table.find("tr", text="Produktionsjahr").parent.parent.parent.td.text year = table.find(text="Produktionsjahr").parent.parent.next_sibling.text
return self.search(name, year) return self.search(name, year)
+4 -4
View File
@@ -34,9 +34,9 @@ class Growl(Notification):
self.growl = notifier.GrowlNotifier( self.growl = notifier.GrowlNotifier(
applicationName = Env.get('appname'), applicationName = Env.get('appname'),
notifications = ["Updates"], notifications = ['Updates'],
defaultNotifications = ["Updates"], defaultNotifications = ['Updates'],
applicationIcon = '%s/static/images/couch.png' % fireEvent('app.api_url', single = True), applicationIcon = self.getNotificationImage('medium'),
hostname = hostname if hostname else 'localhost', hostname = hostname if hostname else 'localhost',
password = password if password else None, password = password if password else None,
port = port if port else 23053 port = port if port else 23053
@@ -56,7 +56,7 @@ class Growl(Notification):
try: try:
self.growl.notify( self.growl.notify(
noteType = "Updates", noteType = 'Updates',
title = self.default_title, title = self.default_title,
description = message, description = message,
sticky = False, sticky = False,
+3 -3
View File
@@ -1,4 +1,4 @@
from couchpotato.core.helpers.variable import getTitle from couchpotato.core.helpers.variable import getTitle, getIdentifier
from couchpotato.core.logger import CPLog from couchpotato.core.logger import CPLog
from couchpotato.core.notifications.base import Notification from couchpotato.core.notifications.base import Notification
@@ -16,7 +16,7 @@ class Trakt(Notification):
'test': 'account/test/%s', 'test': 'account/test/%s',
} }
listen_to = ['movie.downloaded'] listen_to = ['movie.snatched']
def notify(self, message = '', data = None, listener = None): def notify(self, message = '', data = None, listener = None):
if not data: data = {} if not data: data = {}
@@ -38,7 +38,7 @@ class Trakt(Notification):
'username': self.conf('automation_username'), 'username': self.conf('automation_username'),
'password': self.conf('automation_password'), 'password': self.conf('automation_password'),
'movies': [{ 'movies': [{
'imdb_id': data['identifier'], 'imdb_id': getIdentifier(data),
'title': getTitle(data), 'title': getTitle(data),
'year': data['info']['year'] 'year': data['info']['year']
}] if data else [] }] if data else []
+35 -22
View File
@@ -121,15 +121,31 @@ class Plugin(object):
if os.path.exists(path): if os.path.exists(path):
log.debug('%s already exists, overwriting file with new version', path) log.debug('%s already exists, overwriting file with new version', path)
try: write_type = 'w+' if not binary else 'w+b'
f = open(path, 'w+' if not binary else 'w+b')
f.write(content) # Stream file using response object
f.close() if isinstance(content, requests.models.Response):
os.chmod(path, Env.getPermission('file'))
except: # Write file to temp
log.error('Unable writing to file "%s": %s', (path, traceback.format_exc())) with open('%s.tmp' % path, write_type) as f:
if os.path.isfile(path): for chunk in content.iter_content(chunk_size = 1048576):
os.remove(path) if chunk: # filter out keep-alive new chunks
f.write(chunk)
f.flush()
# Rename to destination
os.rename('%s.tmp' % path, path)
else:
try:
f = open(path, write_type)
f.write(content)
f.close()
os.chmod(path, Env.getPermission('file'))
except:
log.error('Unable writing to file "%s": %s', (path, traceback.format_exc()))
if os.path.isfile(path):
os.remove(path)
def makeDir(self, path): def makeDir(self, path):
path = sp(path) path = sp(path)
@@ -146,21 +162,17 @@ class Plugin(object):
folder = sp(folder) folder = sp(folder)
for item in os.listdir(folder): for item in os.listdir(folder):
full_folder = os.path.join(folder, item) full_folder = sp(os.path.join(folder, item))
if not only_clean or (item in only_clean and os.path.isdir(full_folder)): if not only_clean or (item in only_clean and os.path.isdir(full_folder)):
for root, dirs, files in os.walk(full_folder): for subfolder, dirs, files in os.walk(full_folder, topdown = False):
for dir_name in dirs: try:
full_path = os.path.join(root, dir_name) os.rmdir(subfolder)
except:
if len(os.listdir(full_path)) == 0: if show_error:
try: log.info2('Couldn\'t remove directory %s: %s', (subfolder, traceback.format_exc()))
os.rmdir(full_path)
except:
if show_error:
log.info2('Couldn\'t remove directory %s: %s', (full_path, traceback.format_exc()))
try: try:
os.rmdir(folder) os.rmdir(folder)
@@ -169,7 +181,7 @@ class Plugin(object):
log.error('Couldn\'t remove empty directory %s: %s', (folder, traceback.format_exc())) log.error('Couldn\'t remove empty directory %s: %s', (folder, traceback.format_exc()))
# http request # http request
def urlopen(self, url, timeout = 30, data = None, headers = None, files = None, show_error = True): def urlopen(self, url, timeout = 30, data = None, headers = None, files = None, show_error = True, stream = False):
url = quote(ss(url), safe = "%/:=&?~#+!$,;'@()*[]") url = quote(ss(url), safe = "%/:=&?~#+!$,;'@()*[]")
if not headers: headers = {} if not headers: headers = {}
@@ -210,6 +222,7 @@ class Plugin(object):
'timeout': timeout, 'timeout': timeout,
'files': files, 'files': files,
'verify': False, #verify_ssl, Disable for now as to many wrongly implemented certificates.. 'verify': False, #verify_ssl, Disable for now as to many wrongly implemented certificates..
'stream': stream,
} }
method = 'post' if len(data) > 0 or files else 'get' method = 'post' if len(data) > 0 or files else 'get'
@@ -218,7 +231,7 @@ class Plugin(object):
status_code = response.status_code status_code = response.status_code
if response.status_code == requests.codes.ok: if response.status_code == requests.codes.ok:
data = response.content data = response if stream else response.content
else: else:
response.raise_for_status() response.raise_for_status()
+1 -1
View File
@@ -27,7 +27,7 @@ class CategoryPlugin(Plugin):
'desc': 'List all available categories', 'desc': 'List all available categories',
'return': {'type': 'object', 'example': """{ 'return': {'type': 'object', 'example': """{
'success': True, 'success': True,
'list': array, categories 'categories': array, categories
}"""} }"""}
}) })
+3
View File
@@ -64,6 +64,9 @@ class FileManager(Plugin):
def download(self, url = '', dest = None, overwrite = False, urlopen_kwargs = None): def download(self, url = '', dest = None, overwrite = False, urlopen_kwargs = None):
if not urlopen_kwargs: urlopen_kwargs = {} if not urlopen_kwargs: urlopen_kwargs = {}
# Return response object to stream download
urlopen_kwargs['stream'] = True
if not dest: # to Cache if not dest: # to Cache
dest = os.path.join(Env.get('cache_dir'), '%s.%s' % (md5(url), getExt(url))) dest = os.path.join(Env.get('cache_dir'), '%s.%s' % (md5(url), getExt(url)))
+4 -3
View File
@@ -8,7 +8,7 @@ from couchpotato import md5, get_db
from couchpotato.api import addApiView from couchpotato.api import addApiView
from couchpotato.core.event import fireEvent, addEvent from couchpotato.core.event import fireEvent, addEvent
from couchpotato.core.helpers.encoding import toUnicode, sp from couchpotato.core.helpers.encoding import toUnicode, sp
from couchpotato.core.helpers.variable import getTitle from couchpotato.core.helpers.variable import getTitle, tryInt
from couchpotato.core.logger import CPLog from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin from couchpotato.core.plugins.base import Plugin
from .index import ReleaseIndex, ReleaseStatusIndex, ReleaseIDIndex, ReleaseDownloadIndex from .index import ReleaseIndex, ReleaseStatusIndex, ReleaseIDIndex, ReleaseDownloadIndex
@@ -380,6 +380,7 @@ class Release(Plugin):
wait_for = False wait_for = False
let_through = False let_through = False
filtered_results = [] filtered_results = []
minimum_seeders = tryInt(Env.setting('minimum_seeders', section = 'torrent', default = 1))
# Filter out ignored and other releases we don't want # Filter out ignored and other releases we don't want
for rel in results: for rel in results:
@@ -396,8 +397,8 @@ class Release(Plugin):
log.info('Ignored, size "%sMB" to low: %s', (rel['size'], rel['name'])) log.info('Ignored, size "%sMB" to low: %s', (rel['size'], rel['name']))
continue continue
if 'seeders' in rel and rel.get('seeders') <= 0: if 'seeders' in rel and rel.get('seeders') < minimum_seeders:
log.info('Ignored, no seeders: %s', (rel['name'])) log.info('Ignored, not enough seeders, has %s needs %s: %s', (rel.get('seeders'), minimum_seeders, rel['name']))
continue continue
# If a single release comes through the "wait for", let through all # If a single release comes through the "wait for", let through all
+45 -11
View File
@@ -10,7 +10,8 @@ from couchpotato.api import addApiView
from couchpotato.core.event import addEvent, fireEvent, fireEventAsync from couchpotato.core.event import addEvent, fireEvent, fireEventAsync
from couchpotato.core.helpers.encoding import toUnicode, ss, sp from couchpotato.core.helpers.encoding import toUnicode, ss, sp
from couchpotato.core.helpers.variable import getExt, mergeDicts, getTitle, \ from couchpotato.core.helpers.variable import getExt, mergeDicts, getTitle, \
getImdb, link, symlink, tryInt, splitString, fnEscape, isSubFolder, getIdentifier getImdb, link, symlink, tryInt, splitString, fnEscape, isSubFolder, \
getIdentifier, randomString, getFreeSpace, getSize
from couchpotato.core.logger import CPLog from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin from couchpotato.core.plugins.base import Plugin
from couchpotato.environment import Env from couchpotato.environment import Env
@@ -219,6 +220,12 @@ class Renamer(Plugin):
nfo_name = self.conf('nfo_name') nfo_name = self.conf('nfo_name')
separator = self.conf('separator') separator = self.conf('separator')
cd_keys = ['<cd>','<cd_nr>']
if not any(x in folder_name for x in cd_keys) and not any(x in file_name for x in cd_keys):
log.error('Missing `cd` or `cd_nr` in the renamer. This will cause multi-file releases of being renamed to the same file.'
'Force adding it')
file_name = '%s %s' % ('<cd>', file_name)
# Tag release folder as failed_rename in case no groups were found. This prevents check_snatched from removing the release from the downloader. # Tag release folder as failed_rename in case no groups were found. This prevents check_snatched from removing the release from the downloader.
if not groups and self.statusInfoComplete(release_download): if not groups and self.statusInfoComplete(release_download):
self.tagRelease(release_download = release_download, tag = 'failed_rename') self.tagRelease(release_download = release_download, tag = 'failed_rename')
@@ -266,13 +273,14 @@ class Renamer(Plugin):
category_label = category['label'] category_label = category['label']
if category['destination'] and len(category['destination']) > 0 and category['destination'] != 'None': if category['destination'] and len(category['destination']) > 0 and category['destination'] != 'None':
destination = category['destination'] destination = sp(category['destination'])
log.debug('Setting category destination for "%s": %s' % (media_title, destination)) log.debug('Setting category destination for "%s": %s' % (media_title, destination))
else: else:
log.debug('No category destination found for "%s"' % media_title) log.debug('No category destination found for "%s"' % media_title)
except: except:
log.error('Failed getting category label: %s', traceback.format_exc()) log.error('Failed getting category label: %s', traceback.format_exc())
# Find subtitle for renaming # Find subtitle for renaming
group['before_rename'] = [] group['before_rename'] = []
fireEvent('renamer.before', group) fireEvent('renamer.before', group)
@@ -369,6 +377,9 @@ class Renamer(Plugin):
if separator: if separator:
final_file_name = final_file_name.replace(' ', separator) final_file_name = final_file_name.replace(' ', separator)
final_folder_name = ss(final_folder_name)
final_file_name = ss(final_file_name)
# Move DVD files (no structure renaming) # Move DVD files (no structure renaming)
if group['is_dvd'] and file_type is 'movie': if group['is_dvd'] and file_type is 'movie':
found = False found = False
@@ -537,6 +548,13 @@ class Renamer(Plugin):
(not keep_original or self.fileIsAdded(current_file, group)): (not keep_original or self.fileIsAdded(current_file, group)):
remove_files.append(current_file) remove_files.append(current_file)
total_space, available_space = getFreeSpace(destination)
renaming_size = getSize(rename_files.keys())
if renaming_size > available_space:
log.error('Not enough space left, need %s MB but only %s MB available', (renaming_size, available_space))
self.tagRelease(group = group, tag = 'not_enough_space')
continue
# Remove files # Remove files
delete_folders = [] delete_folders = []
for src in remove_files: for src in remove_files:
@@ -552,9 +570,9 @@ class Renamer(Plugin):
os.remove(src) os.remove(src)
parent_dir = os.path.dirname(src) parent_dir = os.path.dirname(src)
if delete_folders.count(parent_dir) == 0 and os.path.isdir(parent_dir) and \ if parent_dir not in delete_folders and os.path.isdir(parent_dir) and \
not isSubFolder(destination, parent_dir) and not isSubFolder(media_folder, parent_dir) and \ not isSubFolder(destination, parent_dir) and not isSubFolder(media_folder, parent_dir) and \
not isSubFolder(parent_dir, base_folder): isSubFolder(parent_dir, base_folder):
delete_folders.append(parent_dir) delete_folders.append(parent_dir)
@@ -563,6 +581,7 @@ class Renamer(Plugin):
self.tagRelease(group = group, tag = 'failed_remove') self.tagRelease(group = group, tag = 'failed_remove')
# Delete leftover folder from older releases # Delete leftover folder from older releases
delete_folders = sorted(delete_folders, key = len, reverse = True)
for delete_folder in delete_folders: for delete_folder in delete_folders:
try: try:
self.deleteEmptyFolder(delete_folder, show_error = False) self.deleteEmptyFolder(delete_folder, show_error = False)
@@ -575,7 +594,10 @@ class Renamer(Plugin):
for src in rename_files: for src in rename_files:
if rename_files[src]: if rename_files[src]:
dst = rename_files[src] dst = rename_files[src]
log.info('Renaming "%s" to "%s"', (src, dst))
if dst in group['renamed_files']:
log.error('File "%s" already renamed once, adding random string at the end to prevent data loss', dst)
dst = '%s.random-%s' % (dst, randomString())
# Create dir # Create dir
self.makeDir(os.path.dirname(dst)) self.makeDir(os.path.dirname(dst))
@@ -617,8 +639,9 @@ class Renamer(Plugin):
group_folder = sp(os.path.join(base_folder, os.path.relpath(group['parentdir'], base_folder).split(os.path.sep)[0])) group_folder = sp(os.path.join(base_folder, os.path.relpath(group['parentdir'], base_folder).split(os.path.sep)[0]))
try: try:
log.info('Deleting folder: %s', group_folder) if self.conf('cleanup') or self.conf('move_leftover'):
self.deleteEmptyFolder(group_folder) log.info('Deleting folder: %s', group_folder)
self.deleteEmptyFolder(group_folder)
except: except:
log.error('Failed removing %s: %s', (group_folder, traceback.format_exc())) log.error('Failed removing %s: %s', (group_folder, traceback.format_exc()))
@@ -774,22 +797,32 @@ Remove it if you want it to be renamed (again, or at least let it try again)
dest = sp(dest) dest = sp(dest)
try: try:
if os.path.exists(dest):
raise Exception('Destination "%s" already exists' % dest)
move_type = self.conf('file_action') move_type = self.conf('file_action')
if use_default: if use_default:
move_type = self.conf('default_file_action') move_type = self.conf('default_file_action')
if move_type not in ['copy', 'link']: if move_type not in ['copy', 'link']:
try: try:
log.info('Moving "%s" to "%s"', (old, dest))
shutil.move(old, dest) shutil.move(old, dest)
except: except:
if os.path.exists(dest): exists = os.path.exists(dest)
if exists and os.path.getsize(old) == os.path.getsize(dest):
log.error('Successfully moved file "%s", but something went wrong: %s', (dest, traceback.format_exc())) log.error('Successfully moved file "%s", but something went wrong: %s', (dest, traceback.format_exc()))
os.unlink(old) os.unlink(old)
else: else:
# remove faultly copied file
if exists:
os.unlink(dest)
raise raise
elif move_type == 'copy': elif move_type == 'copy':
log.info('Copying "%s" to "%s"', (old, dest))
shutil.copy(old, dest) shutil.copy(old, dest)
else: else:
log.info('Linking "%s" to "%s"', (old, dest))
# First try to hardlink # First try to hardlink
try: try:
log.debug('Hardlinking file "%s" to "%s"...', (old, dest)) log.debug('Hardlinking file "%s" to "%s"...', (old, dest))
@@ -799,9 +832,10 @@ Remove it if you want it to be renamed (again, or at least let it try again)
log.debug('Couldn\'t hardlink file "%s" to "%s". Symlinking instead. Error: %s.', (old, dest, traceback.format_exc())) log.debug('Couldn\'t hardlink file "%s" to "%s". Symlinking instead. Error: %s.', (old, dest, traceback.format_exc()))
shutil.copy(old, dest) shutil.copy(old, dest)
try: try:
symlink(dest, old + '.link') old_link = '%s.link' % sp(old)
symlink(dest, old_link)
os.unlink(old) os.unlink(old)
os.rename(old + '.link', old) os.rename(old_link, old)
except: except:
log.error('Couldn\'t symlink file "%s" to "%s". Copied instead. Error: %s. ', (old, dest, traceback.format_exc())) log.error('Couldn\'t symlink file "%s" to "%s". Copied instead. Error: %s. ', (old, dest, traceback.format_exc()))
@@ -1192,7 +1226,7 @@ Remove it if you want it to be renamed (again, or at least let it try again)
except Exception as e: except Exception as e:
log.error('Failed moving left over file %s to %s: %s %s', (leftoverfile, move_to, e, traceback.format_exc())) log.error('Failed moving left over file %s to %s: %s %s', (leftoverfile, move_to, e, traceback.format_exc()))
# As we probably tried to overwrite the nfo file, check if it exists and then remove the original # As we probably tried to overwrite the nfo file, check if it exists and then remove the original
if os.path.isfile(move_to): if os.path.isfile(move_to) and os.path.getsize(leftoverfile) == os.path.getsize(move_to):
if cleanup: if cleanup:
log.info('Deleting left over file %s instead...', leftoverfile) log.info('Deleting left over file %s instead...', leftoverfile)
os.unlink(leftoverfile) os.unlink(leftoverfile)
+17 -8
View File
@@ -9,6 +9,7 @@ import traceback
import warnings import warnings
import re import re
import tarfile import tarfile
import shutil
from CodernityDB.database_super_thread_safe import SuperThreadSafeDatabase from CodernityDB.database_super_thread_safe import SuperThreadSafeDatabase
from argparse import ArgumentParser from argparse import ArgumentParser
@@ -108,14 +109,19 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
if not os.path.isdir(backup_path): os.makedirs(backup_path) if not os.path.isdir(backup_path): os.makedirs(backup_path)
for root, dirs, files in os.walk(backup_path): for root, dirs, files in os.walk(backup_path):
for backup_file in sorted(files): # Only consider files being a direct child of the backup_path
ints = re.findall('\d+', backup_file) if root == backup_path:
for backup_file in sorted(files):
ints = re.findall('\d+', backup_file)
# Delete non zip files # Delete non zip files
if len(ints) != 1: if len(ints) != 1:
os.remove(os.path.join(backup_path, backup_file)) os.remove(os.path.join(root, backup_file))
else: else:
existing_backups.append((int(ints[0]), backup_file)) existing_backups.append((int(ints[0]), backup_file))
else:
# Delete stray directories.
shutil.rmtree(root)
# Remove all but the last 5 # Remove all but the last 5
for eb in existing_backups[:-backup_count]: for eb in existing_backups[:-backup_count]:
@@ -145,12 +151,15 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
if not os.path.exists(python_cache): if not os.path.exists(python_cache):
os.mkdir(python_cache) os.mkdir(python_cache)
session = requests.Session()
session.max_redirects = 5
# Register environment settings # Register environment settings
Env.set('app_dir', sp(base_path)) Env.set('app_dir', sp(base_path))
Env.set('data_dir', sp(data_dir)) Env.set('data_dir', sp(data_dir))
Env.set('log_path', sp(os.path.join(log_dir, 'CouchPotato.log'))) Env.set('log_path', sp(os.path.join(log_dir, 'CouchPotato.log')))
Env.set('db', db) Env.set('db', db)
Env.set('http_opener', requests.Session()) Env.set('http_opener', session)
Env.set('cache_dir', cache_dir) Env.set('cache_dir', cache_dir)
Env.set('cache', FileSystemCache(python_cache)) Env.set('cache', FileSystemCache(python_cache))
Env.set('console_log', options.console_log) Env.set('console_log', options.console_log)
+101 -21
View File
@@ -634,6 +634,7 @@ Option.Directory = new Class({
browser: null, browser: null,
save_on_change: false, save_on_change: false,
use_cache: false, use_cache: false,
current_dir: '',
create: function(){ create: function(){
var self = this; var self = this;
@@ -645,8 +646,17 @@ Option.Directory = new Class({
'click': self.showBrowser.bind(self) 'click': self.showBrowser.bind(self)
} }
}).adopt( }).adopt(
self.input = new Element('span', { self.input = new Element('input', {
'text': self.getSettingValue() 'value': self.getSettingValue(),
'events': {
'change': self.filterDirectory.bind(self),
'keydown': function(e){
if(e.key == 'enter' || e.key == 'tab')
(e).stop();
},
'keyup': self.filterDirectory.bind(self),
'paste': self.filterDirectory.bind(self)
}
}) })
) )
); );
@@ -654,10 +664,55 @@ Option.Directory = new Class({
self.cached = {}; self.cached = {};
}, },
filterDirectory: function(e){
var self = this,
value = self.getValue(),
path_sep = Api.getOption('path_sep'),
active_selector = 'li:not(.blur):not(.empty)';
if(e.key == 'enter' || e.key == 'tab'){
(e).stop();
var first = self.dir_list.getElement(active_selector);
if(first){
self.selectDirectory(first.get('data-value'));
}
}
else {
// New folder
if(value.substr(-1) == path_sep){
if(self.current_dir != value)
self.selectDirectory(value)
}
else {
var pd = self.getParentDir(value);
if(self.current_dir != pd)
self.getDirs(pd);
var folder_filter = value.split(path_sep).getLast()
self.dir_list.getElements('li').each(function(li){
var valid = li.get('text').substr(0, folder_filter.length).toLowerCase() != folder_filter.toLowerCase()
li[valid ? 'addClass' : 'removeClass']('blur')
});
var first = self.dir_list.getElement(active_selector);
if(first){
if(!self.dir_list_scroll)
self.dir_list_scroll = new Fx.Scroll(self.dir_list, {
'transition': 'quint:in:out'
});
self.dir_list_scroll.toElement(first);
}
}
}
},
selectDirectory: function(dir){ selectDirectory: function(dir){
var self = this; var self = this;
self.input.set('text', dir); self.input.set('value', dir);
self.getDirs() self.getDirs()
}, },
@@ -668,9 +723,28 @@ Option.Directory = new Class({
self.selectDirectory(self.getParentDir()) self.selectDirectory(self.getParentDir())
}, },
caretAtEnd: function(){
var self = this;
self.input.focus();
if (typeof self.input.selectionStart == "number") {
self.input.selectionStart = self.input.selectionEnd = self.input.get('value').length;
} else if (typeof el.createTextRange != "undefined") {
self.input.focus();
var range = self.input.createTextRange();
range.collapse(false);
range.select();
}
},
showBrowser: function(){ showBrowser: function(){
var self = this; var self = this;
// Move caret to back of the input
if(!self.browser || self.browser && !self.browser.isVisible())
self.caretAtEnd()
if(!self.browser){ if(!self.browser){
self.browser = new Element('div.directory_list').adopt( self.browser = new Element('div.directory_list').adopt(
new Element('div.pointer'), new Element('div.pointer'),
@@ -686,7 +760,9 @@ Option.Directory = new Class({
}).adopt( }).adopt(
self.show_hidden = new Element('input[type=checkbox].inlay', { self.show_hidden = new Element('input[type=checkbox].inlay', {
'events': { 'events': {
'change': self.getDirs.bind(self) 'change': function(){
self.getDirs()
}
} }
}) })
) )
@@ -707,7 +783,7 @@ Option.Directory = new Class({
'text': 'Clear', 'text': 'Clear',
'events': { 'events': {
'click': function(e){ 'click': function(e){
self.input.set('text', ''); self.input.set('value', '');
self.hideBrowser(e, true); self.hideBrowser(e, true);
} }
} }
@@ -735,7 +811,7 @@ Option.Directory = new Class({
new Form.Check(self.show_hidden); new Form.Check(self.show_hidden);
} }
self.initial_directory = self.input.get('text'); self.initial_directory = self.input.get('value');
self.getDirs(); self.getDirs();
self.browser.show(); self.browser.show();
@@ -749,7 +825,7 @@ Option.Directory = new Class({
if(save) if(save)
self.save(); self.save();
else else
self.input.set('text', self.initial_directory); self.input.set('value', self.initial_directory);
self.browser.hide(); self.browser.hide();
self.el.removeEvents('outerClick') self.el.removeEvents('outerClick')
@@ -757,21 +833,21 @@ Option.Directory = new Class({
}, },
fillBrowser: function(json){ fillBrowser: function(json){
var self = this; var self = this,
v = self.getValue();
self.data = json; self.data = json;
var v = self.getValue(); var previous_dir = json.parent;
var previous_dir = self.getParentDir();
if(v == '') if(v == '')
self.input.set('text', json.home); self.input.set('value', json.home);
if(previous_dir != v && previous_dir.length >= 1 && !json.is_root){ if(previous_dir.length >= 1 && !json.is_root){
var prev_dirname = self.getCurrentDirname(previous_dir); var prev_dirname = self.getCurrentDirname(previous_dir);
if(previous_dir == json.home) if(previous_dir == json.home)
prev_dirname = 'Home'; prev_dirname = 'Home Folder';
else if(previous_dir == '/' && json.platform == 'nt') else if(previous_dir == '/' && json.platform == 'nt')
prev_dirname = 'Computer'; prev_dirname = 'Computer';
@@ -801,12 +877,13 @@ Option.Directory = new Class({
new Element('li.empty', { new Element('li.empty', {
'text': 'Selected folder is empty' 'text': 'Selected folder is empty'
}).inject(self.dir_list) }).inject(self.dir_list)
self.caretAtEnd();
}, },
getDirs: function(){ getDirs: function(dir){
var self = this; var self = this,
c = dir || self.getValue();
var c = self.getValue();
if(self.cached[c] && self.use_cache){ if(self.cached[c] && self.use_cache){
self.fillBrowser() self.fillBrowser()
@@ -817,7 +894,10 @@ Option.Directory = new Class({
'path': c, 'path': c,
'show_hidden': +self.show_hidden.checked 'show_hidden': +self.show_hidden.checked
}, },
'onComplete': self.fillBrowser.bind(self) 'onComplete': function(json){
self.current_dir = c;
self.fillBrowser(json);
}
}) })
} }
}, },
@@ -831,8 +911,8 @@ Option.Directory = new Class({
var v = dir || self.getValue(); var v = dir || self.getValue();
var sep = Api.getOption('path_sep'); var sep = Api.getOption('path_sep');
var dirs = v.split(sep); var dirs = v.split(sep);
if(dirs.pop() == '') if(dirs.pop() == '')
dirs.pop(); dirs.pop();
return dirs.join(sep) + sep return dirs.join(sep) + sep
}, },
@@ -845,7 +925,7 @@ Option.Directory = new Class({
getValue: function(){ getValue: function(){
var self = this; var self = this;
return self.input.get('text'); return self.input.get('value');
} }
}); });
+11 -2
View File
@@ -302,15 +302,19 @@
font-family: 'Elusive-Icons'; font-family: 'Elusive-Icons';
color: #f5e39c; color: #f5e39c;
} }
.page form .directory > span { .page form .directory > input {
height: 25px; height: 25px;
display: inline-block; display: inline-block;
float: right; float: right;
text-align: right; text-align: right;
white-space: nowrap; white-space: nowrap;
cursor: pointer; cursor: pointer;
background: none;
border: 0;
color: #FFF;
width: 100%;
} }
.page form .directory span:empty:before { .page form .directory input:empty:before {
content: 'No folder selected'; content: 'No folder selected';
font-style: italic; font-style: italic;
opacity: .3; opacity: .3;
@@ -353,6 +357,11 @@
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.page .directory_list li.blur {
opacity: .3;
}
.page .directory_list li:last-child { .page .directory_list li:last-child {
border-bottom: 1px solid rgba(255,255,255,0.1); border-bottom: 1px solid rgba(255,255,255,0.1);
} }
+4
View File
@@ -532,7 +532,11 @@ class Session(SessionRedirectMixin):
if not isinstance(request, PreparedRequest): if not isinstance(request, PreparedRequest):
raise ValueError('You can only send PreparedRequests.') raise ValueError('You can only send PreparedRequests.')
redirect_count = 0
while request.url in self.redirect_cache: while request.url in self.redirect_cache:
redirect_count += 1
if redirect_count > self.max_redirects:
raise TooManyRedirects
request.url = self.redirect_cache.get(request.url) request.url = self.redirect_cache.get(request.url)
# Set up variables needed for resolve_redirects and dispatching of hooks # Set up variables needed for resolve_redirects and dispatching of hooks