Merge branch 'develop'
This commit is contained in:
@@ -286,13 +286,13 @@ config = [{
|
||||
'name': 'permission_folder',
|
||||
'default': '0755',
|
||||
'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',
|
||||
'default': '0755',
|
||||
'label': 'File CHMOD',
|
||||
'description': 'Same as Folder CHMOD but for files',
|
||||
'description': 'See Folder CHMOD description, but for files',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -25,7 +25,7 @@ class Transmission(DownloaderBase):
|
||||
|
||||
def connect(self):
|
||||
# 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]):
|
||||
log.error('Config properties are not filled in correctly, port is missing.')
|
||||
return False
|
||||
@@ -162,11 +162,11 @@ class Transmission(DownloaderBase):
|
||||
class TransmissionRPC(object):
|
||||
|
||||
"""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__()
|
||||
|
||||
self.url = 'http://' + host + ':' + str(port) + '/' + rpc_url + '/rpc'
|
||||
self.url = host + ':' + str(port) + '/' + rpc_url + '/rpc'
|
||||
self.tag = 0
|
||||
self.session_id = 0
|
||||
self.session = {}
|
||||
@@ -274,8 +274,8 @@ config = [{
|
||||
},
|
||||
{
|
||||
'name': 'host',
|
||||
'default': 'localhost:9091',
|
||||
'description': 'Hostname with port. Usually <strong>localhost:9091</strong>',
|
||||
'default': 'http://localhost:9091',
|
||||
'description': 'Hostname with port. Usually <strong>http://localhost:9091</strong>',
|
||||
},
|
||||
{
|
||||
'name': 'rpc_url',
|
||||
|
||||
@@ -382,6 +382,28 @@ def getFreeSpace(directories):
|
||||
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):
|
||||
for item in iterable:
|
||||
if func(item):
|
||||
|
||||
@@ -42,6 +42,7 @@ class Base(TorrentProvider):
|
||||
|
||||
link = result.find('span', attrs = {'class': 'torrent_name_link'}).parent
|
||||
url = result.find('td', attrs = {'class': 'torrent_td'}).find('a')
|
||||
tds = result.find_all('td')
|
||||
|
||||
results.append({
|
||||
'id': link['href'].replace('torrents.php?torrentid=', ''),
|
||||
@@ -49,8 +50,8 @@ class Base(TorrentProvider):
|
||||
'url': self.urls['download'] % url['href'],
|
||||
'detail_url': self.urls['download'] % link['href'],
|
||||
'size': self.parseSize(result.find_all('td')[5].string),
|
||||
'seeders': tryInt(result.find_all('td')[7].string),
|
||||
'leechers': tryInt(result.find_all('td')[8].string),
|
||||
'seeders': tryInt(tds[len(tds)-2].string),
|
||||
'leechers': tryInt(tds[len(tds)-1].string),
|
||||
})
|
||||
|
||||
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'
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}]
|
||||
|
||||
@@ -27,6 +27,10 @@ class MovieBase(MovieTypeBase):
|
||||
|
||||
addApiView('movie.add', self.addView, docs = {
|
||||
'desc': 'Add new movie to the wanted list',
|
||||
'return': {'type': 'object', 'example': """{
|
||||
'success': True,
|
||||
'movie': object
|
||||
}"""},
|
||||
'params': {
|
||||
'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.'},
|
||||
@@ -150,7 +154,7 @@ class MovieBase(MovieTypeBase):
|
||||
for release in fireEvent('release.for_media', m['_id'], single = True):
|
||||
if release.get('status') in ['downloaded', 'snatched', 'seeding', 'done']:
|
||||
if params.get('ignore_previous', False):
|
||||
fireEvent('release.update_status', m['_id'], status = 'ignored')
|
||||
fireEvent('release.update_status', release['_id'], status = 'ignored')
|
||||
else:
|
||||
fireEvent('release.delete', release['_id'], single = True)
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import base64
|
||||
import time
|
||||
|
||||
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.media.movie.providers.base import MovieProvider
|
||||
from couchpotato.environment import Env
|
||||
@@ -66,7 +66,7 @@ class CouchPotatoApi(MovieProvider):
|
||||
if not name:
|
||||
return
|
||||
|
||||
name_enc = base64.b64encode(name)
|
||||
name_enc = base64.b64encode(ss(name))
|
||||
return self.getJsonData(self.urls['validate'] % name_enc, headers = self.getRequestHeaders())
|
||||
|
||||
def isMovie(self, identifier = None):
|
||||
|
||||
@@ -25,6 +25,6 @@ class Filmstarts(UserscriptBase):
|
||||
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 = 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)
|
||||
|
||||
@@ -34,9 +34,9 @@ class Growl(Notification):
|
||||
|
||||
self.growl = notifier.GrowlNotifier(
|
||||
applicationName = Env.get('appname'),
|
||||
notifications = ["Updates"],
|
||||
defaultNotifications = ["Updates"],
|
||||
applicationIcon = '%s/static/images/couch.png' % fireEvent('app.api_url', single = True),
|
||||
notifications = ['Updates'],
|
||||
defaultNotifications = ['Updates'],
|
||||
applicationIcon = self.getNotificationImage('medium'),
|
||||
hostname = hostname if hostname else 'localhost',
|
||||
password = password if password else None,
|
||||
port = port if port else 23053
|
||||
@@ -56,7 +56,7 @@ class Growl(Notification):
|
||||
|
||||
try:
|
||||
self.growl.notify(
|
||||
noteType = "Updates",
|
||||
noteType = 'Updates',
|
||||
title = self.default_title,
|
||||
description = message,
|
||||
sticky = False,
|
||||
|
||||
@@ -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.notifications.base import Notification
|
||||
|
||||
@@ -16,7 +16,7 @@ class Trakt(Notification):
|
||||
'test': 'account/test/%s',
|
||||
}
|
||||
|
||||
listen_to = ['movie.downloaded']
|
||||
listen_to = ['movie.snatched']
|
||||
|
||||
def notify(self, message = '', data = None, listener = None):
|
||||
if not data: data = {}
|
||||
@@ -38,7 +38,7 @@ class Trakt(Notification):
|
||||
'username': self.conf('automation_username'),
|
||||
'password': self.conf('automation_password'),
|
||||
'movies': [{
|
||||
'imdb_id': data['identifier'],
|
||||
'imdb_id': getIdentifier(data),
|
||||
'title': getTitle(data),
|
||||
'year': data['info']['year']
|
||||
}] if data else []
|
||||
|
||||
@@ -121,15 +121,31 @@ class Plugin(object):
|
||||
if os.path.exists(path):
|
||||
log.debug('%s already exists, overwriting file with new version', path)
|
||||
|
||||
try:
|
||||
f = open(path, 'w+' if not binary else 'w+b')
|
||||
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)
|
||||
write_type = 'w+' if not binary else 'w+b'
|
||||
|
||||
# Stream file using response object
|
||||
if isinstance(content, requests.models.Response):
|
||||
|
||||
# Write file to temp
|
||||
with open('%s.tmp' % path, write_type) as f:
|
||||
for chunk in content.iter_content(chunk_size = 1048576):
|
||||
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):
|
||||
path = sp(path)
|
||||
@@ -146,21 +162,17 @@ class Plugin(object):
|
||||
folder = sp(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)):
|
||||
|
||||
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:
|
||||
full_path = os.path.join(root, dir_name)
|
||||
|
||||
if len(os.listdir(full_path)) == 0:
|
||||
try:
|
||||
os.rmdir(full_path)
|
||||
except:
|
||||
if show_error:
|
||||
log.info2('Couldn\'t remove directory %s: %s', (full_path, traceback.format_exc()))
|
||||
try:
|
||||
os.rmdir(subfolder)
|
||||
except:
|
||||
if show_error:
|
||||
log.info2('Couldn\'t remove directory %s: %s', (subfolder, traceback.format_exc()))
|
||||
|
||||
try:
|
||||
os.rmdir(folder)
|
||||
@@ -169,7 +181,7 @@ class Plugin(object):
|
||||
log.error('Couldn\'t remove empty directory %s: %s', (folder, traceback.format_exc()))
|
||||
|
||||
# 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 = "%/:=&?~#+!$,;'@()*[]")
|
||||
|
||||
if not headers: headers = {}
|
||||
@@ -210,6 +222,7 @@ class Plugin(object):
|
||||
'timeout': timeout,
|
||||
'files': files,
|
||||
'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'
|
||||
|
||||
@@ -218,7 +231,7 @@ class Plugin(object):
|
||||
|
||||
status_code = response.status_code
|
||||
if response.status_code == requests.codes.ok:
|
||||
data = response.content
|
||||
data = response if stream else response.content
|
||||
else:
|
||||
response.raise_for_status()
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ class CategoryPlugin(Plugin):
|
||||
'desc': 'List all available categories',
|
||||
'return': {'type': 'object', 'example': """{
|
||||
'success': True,
|
||||
'list': array, categories
|
||||
'categories': array, categories
|
||||
}"""}
|
||||
})
|
||||
|
||||
|
||||
@@ -64,6 +64,9 @@ class FileManager(Plugin):
|
||||
def download(self, url = '', dest = None, overwrite = False, urlopen_kwargs = None):
|
||||
if not urlopen_kwargs: urlopen_kwargs = {}
|
||||
|
||||
# Return response object to stream download
|
||||
urlopen_kwargs['stream'] = True
|
||||
|
||||
if not dest: # to Cache
|
||||
dest = os.path.join(Env.get('cache_dir'), '%s.%s' % (md5(url), getExt(url)))
|
||||
|
||||
@@ -107,4 +110,4 @@ class FileManager(Plugin):
|
||||
else:
|
||||
log.info('Subfolder test succeeded')
|
||||
|
||||
return failed == 0
|
||||
return failed == 0
|
||||
|
||||
@@ -8,7 +8,7 @@ from couchpotato import md5, get_db
|
||||
from couchpotato.api import addApiView
|
||||
from couchpotato.core.event import fireEvent, addEvent
|
||||
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.plugins.base import Plugin
|
||||
from .index import ReleaseIndex, ReleaseStatusIndex, ReleaseIDIndex, ReleaseDownloadIndex
|
||||
@@ -380,6 +380,7 @@ class Release(Plugin):
|
||||
wait_for = False
|
||||
let_through = False
|
||||
filtered_results = []
|
||||
minimum_seeders = tryInt(Env.setting('minimum_seeders', section = 'torrent', default = 1))
|
||||
|
||||
# Filter out ignored and other releases we don't want
|
||||
for rel in results:
|
||||
@@ -396,8 +397,8 @@ class Release(Plugin):
|
||||
log.info('Ignored, size "%sMB" to low: %s', (rel['size'], rel['name']))
|
||||
continue
|
||||
|
||||
if 'seeders' in rel and rel.get('seeders') <= 0:
|
||||
log.info('Ignored, no seeders: %s', (rel['name']))
|
||||
if 'seeders' in rel and rel.get('seeders') < minimum_seeders:
|
||||
log.info('Ignored, not enough seeders, has %s needs %s: %s', (rel.get('seeders'), minimum_seeders, rel['name']))
|
||||
continue
|
||||
|
||||
# If a single release comes through the "wait for", let through all
|
||||
|
||||
@@ -10,7 +10,8 @@ from couchpotato.api import addApiView
|
||||
from couchpotato.core.event import addEvent, fireEvent, fireEventAsync
|
||||
from couchpotato.core.helpers.encoding import toUnicode, ss, sp
|
||||
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.plugins.base import Plugin
|
||||
from couchpotato.environment import Env
|
||||
@@ -219,6 +220,12 @@ class Renamer(Plugin):
|
||||
nfo_name = self.conf('nfo_name')
|
||||
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.
|
||||
if not groups and self.statusInfoComplete(release_download):
|
||||
self.tagRelease(release_download = release_download, tag = 'failed_rename')
|
||||
@@ -266,13 +273,14 @@ class Renamer(Plugin):
|
||||
category_label = category['label']
|
||||
|
||||
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))
|
||||
else:
|
||||
log.debug('No category destination found for "%s"' % media_title)
|
||||
except:
|
||||
log.error('Failed getting category label: %s', traceback.format_exc())
|
||||
|
||||
|
||||
# Find subtitle for renaming
|
||||
group['before_rename'] = []
|
||||
fireEvent('renamer.before', group)
|
||||
@@ -369,6 +377,9 @@ class Renamer(Plugin):
|
||||
if 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)
|
||||
if group['is_dvd'] and file_type is 'movie':
|
||||
found = False
|
||||
@@ -537,6 +548,13 @@ class Renamer(Plugin):
|
||||
(not keep_original or self.fileIsAdded(current_file, group)):
|
||||
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
|
||||
delete_folders = []
|
||||
for src in remove_files:
|
||||
@@ -552,9 +570,9 @@ class Renamer(Plugin):
|
||||
os.remove(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(parent_dir, base_folder):
|
||||
isSubFolder(parent_dir, base_folder):
|
||||
|
||||
delete_folders.append(parent_dir)
|
||||
|
||||
@@ -563,6 +581,7 @@ class Renamer(Plugin):
|
||||
self.tagRelease(group = group, tag = 'failed_remove')
|
||||
|
||||
# Delete leftover folder from older releases
|
||||
delete_folders = sorted(delete_folders, key = len, reverse = True)
|
||||
for delete_folder in delete_folders:
|
||||
try:
|
||||
self.deleteEmptyFolder(delete_folder, show_error = False)
|
||||
@@ -575,7 +594,10 @@ class Renamer(Plugin):
|
||||
for src in rename_files:
|
||||
if 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
|
||||
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]))
|
||||
|
||||
try:
|
||||
log.info('Deleting folder: %s', group_folder)
|
||||
self.deleteEmptyFolder(group_folder)
|
||||
if self.conf('cleanup') or self.conf('move_leftover'):
|
||||
log.info('Deleting folder: %s', group_folder)
|
||||
self.deleteEmptyFolder(group_folder)
|
||||
except:
|
||||
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)
|
||||
try:
|
||||
|
||||
if os.path.exists(dest):
|
||||
raise Exception('Destination "%s" already exists' % dest)
|
||||
|
||||
move_type = self.conf('file_action')
|
||||
if use_default:
|
||||
move_type = self.conf('default_file_action')
|
||||
|
||||
if move_type not in ['copy', 'link']:
|
||||
try:
|
||||
log.info('Moving "%s" to "%s"', (old, dest))
|
||||
shutil.move(old, dest)
|
||||
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()))
|
||||
os.unlink(old)
|
||||
else:
|
||||
# remove faultly copied file
|
||||
if exists:
|
||||
os.unlink(dest)
|
||||
raise
|
||||
elif move_type == 'copy':
|
||||
log.info('Copying "%s" to "%s"', (old, dest))
|
||||
shutil.copy(old, dest)
|
||||
else:
|
||||
log.info('Linking "%s" to "%s"', (old, dest))
|
||||
# First try to hardlink
|
||||
try:
|
||||
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()))
|
||||
shutil.copy(old, dest)
|
||||
try:
|
||||
symlink(dest, old + '.link')
|
||||
old_link = '%s.link' % sp(old)
|
||||
symlink(dest, old_link)
|
||||
os.unlink(old)
|
||||
os.rename(old + '.link', old)
|
||||
os.rename(old_link, old)
|
||||
except:
|
||||
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:
|
||||
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
|
||||
if os.path.isfile(move_to):
|
||||
if os.path.isfile(move_to) and os.path.getsize(leftoverfile) == os.path.getsize(move_to):
|
||||
if cleanup:
|
||||
log.info('Deleting left over file %s instead...', leftoverfile)
|
||||
os.unlink(leftoverfile)
|
||||
|
||||
+17
-8
@@ -9,6 +9,7 @@ import traceback
|
||||
import warnings
|
||||
import re
|
||||
import tarfile
|
||||
import shutil
|
||||
|
||||
from CodernityDB.database_super_thread_safe import SuperThreadSafeDatabase
|
||||
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)
|
||||
|
||||
for root, dirs, files in os.walk(backup_path):
|
||||
for backup_file in sorted(files):
|
||||
ints = re.findall('\d+', backup_file)
|
||||
# Only consider files being a direct child of the backup_path
|
||||
if root == backup_path:
|
||||
for backup_file in sorted(files):
|
||||
ints = re.findall('\d+', backup_file)
|
||||
|
||||
# Delete non zip files
|
||||
if len(ints) != 1:
|
||||
os.remove(os.path.join(backup_path, backup_file))
|
||||
else:
|
||||
existing_backups.append((int(ints[0]), backup_file))
|
||||
# Delete non zip files
|
||||
if len(ints) != 1:
|
||||
os.remove(os.path.join(root, backup_file))
|
||||
else:
|
||||
existing_backups.append((int(ints[0]), backup_file))
|
||||
else:
|
||||
# Delete stray directories.
|
||||
shutil.rmtree(root)
|
||||
|
||||
# Remove all but the last 5
|
||||
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):
|
||||
os.mkdir(python_cache)
|
||||
|
||||
session = requests.Session()
|
||||
session.max_redirects = 5
|
||||
|
||||
# Register environment settings
|
||||
Env.set('app_dir', sp(base_path))
|
||||
Env.set('data_dir', sp(data_dir))
|
||||
Env.set('log_path', sp(os.path.join(log_dir, 'CouchPotato.log')))
|
||||
Env.set('db', db)
|
||||
Env.set('http_opener', requests.Session())
|
||||
Env.set('http_opener', session)
|
||||
Env.set('cache_dir', cache_dir)
|
||||
Env.set('cache', FileSystemCache(python_cache))
|
||||
Env.set('console_log', options.console_log)
|
||||
|
||||
@@ -634,6 +634,7 @@ Option.Directory = new Class({
|
||||
browser: null,
|
||||
save_on_change: false,
|
||||
use_cache: false,
|
||||
current_dir: '',
|
||||
|
||||
create: function(){
|
||||
var self = this;
|
||||
@@ -645,8 +646,17 @@ Option.Directory = new Class({
|
||||
'click': self.showBrowser.bind(self)
|
||||
}
|
||||
}).adopt(
|
||||
self.input = new Element('span', {
|
||||
'text': self.getSettingValue()
|
||||
self.input = new Element('input', {
|
||||
'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 = {};
|
||||
},
|
||||
|
||||
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){
|
||||
var self = this;
|
||||
|
||||
self.input.set('text', dir);
|
||||
self.input.set('value', dir);
|
||||
|
||||
self.getDirs()
|
||||
},
|
||||
@@ -668,9 +723,28 @@ Option.Directory = new Class({
|
||||
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(){
|
||||
var self = this;
|
||||
|
||||
// Move caret to back of the input
|
||||
if(!self.browser || self.browser && !self.browser.isVisible())
|
||||
self.caretAtEnd()
|
||||
|
||||
if(!self.browser){
|
||||
self.browser = new Element('div.directory_list').adopt(
|
||||
new Element('div.pointer'),
|
||||
@@ -686,7 +760,9 @@ Option.Directory = new Class({
|
||||
}).adopt(
|
||||
self.show_hidden = new Element('input[type=checkbox].inlay', {
|
||||
'events': {
|
||||
'change': self.getDirs.bind(self)
|
||||
'change': function(){
|
||||
self.getDirs()
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
@@ -707,7 +783,7 @@ Option.Directory = new Class({
|
||||
'text': 'Clear',
|
||||
'events': {
|
||||
'click': function(e){
|
||||
self.input.set('text', '');
|
||||
self.input.set('value', '');
|
||||
self.hideBrowser(e, true);
|
||||
}
|
||||
}
|
||||
@@ -735,7 +811,7 @@ Option.Directory = new Class({
|
||||
new Form.Check(self.show_hidden);
|
||||
}
|
||||
|
||||
self.initial_directory = self.input.get('text');
|
||||
self.initial_directory = self.input.get('value');
|
||||
|
||||
self.getDirs();
|
||||
self.browser.show();
|
||||
@@ -749,7 +825,7 @@ Option.Directory = new Class({
|
||||
if(save)
|
||||
self.save();
|
||||
else
|
||||
self.input.set('text', self.initial_directory);
|
||||
self.input.set('value', self.initial_directory);
|
||||
|
||||
self.browser.hide();
|
||||
self.el.removeEvents('outerClick')
|
||||
@@ -757,21 +833,21 @@ Option.Directory = new Class({
|
||||
},
|
||||
|
||||
fillBrowser: function(json){
|
||||
var self = this;
|
||||
var self = this,
|
||||
v = self.getValue();
|
||||
|
||||
self.data = json;
|
||||
|
||||
var v = self.getValue();
|
||||
var previous_dir = self.getParentDir();
|
||||
var previous_dir = json.parent;
|
||||
|
||||
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);
|
||||
if(previous_dir == json.home)
|
||||
prev_dirname = 'Home';
|
||||
prev_dirname = 'Home Folder';
|
||||
else if(previous_dir == '/' && json.platform == 'nt')
|
||||
prev_dirname = 'Computer';
|
||||
|
||||
@@ -801,12 +877,13 @@ Option.Directory = new Class({
|
||||
new Element('li.empty', {
|
||||
'text': 'Selected folder is empty'
|
||||
}).inject(self.dir_list)
|
||||
|
||||
self.caretAtEnd();
|
||||
},
|
||||
|
||||
getDirs: function(){
|
||||
var self = this;
|
||||
|
||||
var c = self.getValue();
|
||||
getDirs: function(dir){
|
||||
var self = this,
|
||||
c = dir || self.getValue();
|
||||
|
||||
if(self.cached[c] && self.use_cache){
|
||||
self.fillBrowser()
|
||||
@@ -817,7 +894,10 @@ Option.Directory = new Class({
|
||||
'path': c,
|
||||
'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 sep = Api.getOption('path_sep');
|
||||
var dirs = v.split(sep);
|
||||
if(dirs.pop() == '')
|
||||
dirs.pop();
|
||||
if(dirs.pop() == '')
|
||||
dirs.pop();
|
||||
|
||||
return dirs.join(sep) + sep
|
||||
},
|
||||
@@ -845,7 +925,7 @@ Option.Directory = new Class({
|
||||
|
||||
getValue: function(){
|
||||
var self = this;
|
||||
return self.input.get('text');
|
||||
return self.input.get('value');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -302,15 +302,19 @@
|
||||
font-family: 'Elusive-Icons';
|
||||
color: #f5e39c;
|
||||
}
|
||||
.page form .directory > span {
|
||||
.page form .directory > input {
|
||||
height: 25px;
|
||||
display: inline-block;
|
||||
float: right;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
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';
|
||||
font-style: italic;
|
||||
opacity: .3;
|
||||
@@ -353,6 +357,11 @@
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.page .directory_list li.blur {
|
||||
opacity: .3;
|
||||
}
|
||||
|
||||
.page .directory_list li:last-child {
|
||||
border-bottom: 1px solid rgba(255,255,255,0.1);
|
||||
}
|
||||
|
||||
@@ -532,7 +532,11 @@ class Session(SessionRedirectMixin):
|
||||
if not isinstance(request, PreparedRequest):
|
||||
raise ValueError('You can only send PreparedRequests.')
|
||||
|
||||
redirect_count = 0
|
||||
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)
|
||||
|
||||
# Set up variables needed for resolve_redirects and dispatching of hooks
|
||||
|
||||
Reference in New Issue
Block a user