Merge branch 'refs/heads/develop'
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import collections
|
||||
import ctypes
|
||||
import hashlib
|
||||
import os
|
||||
import platform
|
||||
@@ -291,9 +292,14 @@ def dictIsSubset(a, b):
|
||||
return all([k in b and b[k] == v for k, v in a.items()])
|
||||
|
||||
|
||||
# Returns True if sub_folder is the same as or inside base_folder
|
||||
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)
|
||||
if base_folder and sub_folder:
|
||||
base = sp(os.path.realpath(base_folder)) + os.path.sep
|
||||
subfolder = sp(os.path.realpath(sub_folder)) + os.path.sep
|
||||
return os.path.commonprefix([subfolder, base]) == base
|
||||
|
||||
return False
|
||||
|
||||
|
||||
# From SABNZBD
|
||||
@@ -341,3 +347,37 @@ def removePyc(folder, only_excess = True, show_logs = True):
|
||||
os.rmdir(full_path)
|
||||
except:
|
||||
log.error('Couldn\'t remove empty directory %s: %s', (full_path, traceback.format_exc()))
|
||||
|
||||
|
||||
def getFreeSpace(directories):
|
||||
|
||||
single = not isinstance(directories, (tuple, list))
|
||||
if single:
|
||||
directories = [directories]
|
||||
|
||||
free_space = {}
|
||||
for folder in 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)]
|
||||
|
||||
if single: return size
|
||||
|
||||
free_space[folder] = size
|
||||
|
||||
return free_space
|
||||
|
||||
@@ -477,7 +477,8 @@ class MediaPlugin(MediaBase):
|
||||
if fireEvent('quality.isfinish', {'identifier': release['quality'], 'is_3d': release.get('is_3d', False)}, profile, timedelta(seconds = time.time() - release['last_edit']).days, single = True):
|
||||
m['status'] = 'done'
|
||||
except RecordNotFound:
|
||||
log.debug('Failed restatus: %s', traceback.format_exc())
|
||||
log.debug('Failed restatus, keeping previous: %s', traceback.format_exc())
|
||||
m['status'] = previous_status
|
||||
|
||||
# Only update when status has changed
|
||||
if previous_status != m['status']:
|
||||
|
||||
@@ -5,7 +5,7 @@ from couchpotato import get_db
|
||||
from couchpotato.api import addApiView
|
||||
from couchpotato.core.event import addEvent, fireEvent
|
||||
from couchpotato.core.helpers.encoding import toUnicode
|
||||
from couchpotato.core.helpers.variable import md5, getExt
|
||||
from couchpotato.core.helpers.variable import md5, getExt, isSubFolder
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.environment import Env
|
||||
@@ -32,6 +32,8 @@ class FileManager(Plugin):
|
||||
|
||||
fireEvent('schedule.interval', 'file.cleanup', self.cleanup, hours = 24)
|
||||
|
||||
addEvent('app.test', self.doSubfolderTest)
|
||||
|
||||
def cleanup(self):
|
||||
|
||||
# Wait a bit after starting before cleanup
|
||||
@@ -76,3 +78,33 @@ class FileManager(Plugin):
|
||||
|
||||
self.createFile(dest, filedata, binary = True)
|
||||
return dest
|
||||
|
||||
def doSubfolderTest(self):
|
||||
|
||||
tests = {
|
||||
('/test/subfolder', '/test/sub'): False,
|
||||
('/test/sub/folder', '/test/sub'): True,
|
||||
('/test/sub/folder', '/test/sub2'): False,
|
||||
('/sub/fold', '/test/sub/fold'): False,
|
||||
('/sub/fold', '/test/sub/folder'): False,
|
||||
('/opt/couchpotato', '/var/opt/couchpotato'): False,
|
||||
('/var/opt', '/var/opt/couchpotato'): False,
|
||||
('/CapItaLs/Are/OK', '/CapItaLs/Are/OK'): True,
|
||||
('/CapItaLs/Are/OK', '/CapItaLs/Are/OK2'): False,
|
||||
('/capitals/are/not/OK', '/capitals/are/NOT'): False,
|
||||
('\\\\Mounted\\Volume\\Test', '\\\\Mounted\\Volume'): True,
|
||||
('C:\\\\test\\path', 'C:\\\\test2'): False
|
||||
}
|
||||
|
||||
failed = 0
|
||||
for x in tests:
|
||||
if isSubFolder(x[0], x[1]) is not tests[x]:
|
||||
log.error('Failed subfolder test %s %s', x)
|
||||
failed += 1
|
||||
|
||||
if failed > 0:
|
||||
log.error('Subfolder test failed %s tests', failed)
|
||||
else:
|
||||
log.info('Subfolder test succeeded')
|
||||
|
||||
return failed == 0
|
||||
@@ -1,6 +1,4 @@
|
||||
import ctypes
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
|
||||
@@ -8,7 +6,7 @@ from couchpotato import get_db
|
||||
from couchpotato.api import addApiView
|
||||
from couchpotato.core.event import fireEvent, addEvent, fireEventAsync
|
||||
from couchpotato.core.helpers.encoding import sp
|
||||
from couchpotato.core.helpers.variable import splitString, getTitle, tryInt, getIdentifier
|
||||
from couchpotato.core.helpers.variable import splitString, getTitle, tryInt, getIdentifier, getFreeSpace
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.environment import Env
|
||||
@@ -273,31 +271,7 @@ class Manage(Plugin):
|
||||
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
|
||||
return getFreeSpace(self.directories())
|
||||
|
||||
|
||||
config = [{
|
||||
|
||||
@@ -47,14 +47,14 @@ var Profile = new Class({
|
||||
new Element('div.wait_for.ctrlHolder').adopt(
|
||||
// "Wait the entered number of days for a checked quality, before downloading a lower quality release."
|
||||
new Element('span', {'text':'Wait'}),
|
||||
new Element('input.inlay.xsmall', {
|
||||
new Element('input.inlay.wait_for_input.xsmall', {
|
||||
'type':'text',
|
||||
'value': data.wait_for && data.wait_for.length > 0 ? data.wait_for[0] : 0
|
||||
}),
|
||||
new Element('span', {'text':'day(s) for a better quality '}),
|
||||
new Element('span.advanced', {'text':'and keep searching'}),
|
||||
// "After a checked quality is found and downloaded, continue searching for even better quality releases for the entered number of days."
|
||||
new Element('input.inlay.xsmall.advanced', {
|
||||
new Element('input.inlay.xsmall.stop_after_input.advanced', {
|
||||
'type':'text',
|
||||
'value': data.stop_after && data.stop_after.length > 0 ? data.stop_after[0] : 0
|
||||
}),
|
||||
@@ -124,8 +124,8 @@ var Profile = new Class({
|
||||
var data = {
|
||||
'id' : self.data._id,
|
||||
'label' : self.el.getElement('.quality_label input').get('value'),
|
||||
'wait_for' : self.el.getElement('.wait_for input').get('value'),
|
||||
'stop_after' : self.el.getElement('.stop_after input').get('value'),
|
||||
'wait_for' : self.el.getElement('.wait_for_input').get('value'),
|
||||
'stop_after' : self.el.getElement('.stop_after_input').get('value'),
|
||||
'types': []
|
||||
};
|
||||
|
||||
|
||||
@@ -387,8 +387,8 @@ class QualityPlugin(Plugin):
|
||||
try:
|
||||
index = [i for i, identifier in enumerate(profile['qualities']) if identifier == quality['identifier'] and bool(profile['3d'][i] if profile.get('3d') else False) == bool(quality.get('is_3d', False))][0]
|
||||
|
||||
if index == 0 or profile['finish'][index] and int(release_age) >= int(profile['stop_after'][0]):
|
||||
return True
|
||||
if index == 0 or (profile['finish'][index] and int(release_age) >= int(profile.get('stop_after', [0])[0])):
|
||||
return True
|
||||
|
||||
return False
|
||||
except:
|
||||
|
||||
@@ -639,9 +639,9 @@ class Scanner(Plugin):
|
||||
|
||||
# Try with other
|
||||
if len(movie) == 0 and name_year.get('other') and name_year['other'].get('name') and name_year['other'].get('year'):
|
||||
search_q2 = '%(name)s %(year)s' % name_year
|
||||
search_q2 = '%(name)s %(year)s' % name_year.get('other')
|
||||
if search_q2 != search_q:
|
||||
movie = fireEvent('movie.search', q = '%(name)s %(year)s' % name_year.get('other'), merge = True, limit = 1)
|
||||
movie = fireEvent('movie.search', q = search_q2, merge = True, limit = 1)
|
||||
|
||||
if len(movie) > 0:
|
||||
imdb_id = movie[0].get('imdb')
|
||||
|
||||
@@ -32,7 +32,7 @@ class Trailer(Plugin):
|
||||
destination = os.path.join(group['destination_dir'], filename)
|
||||
if not os.path.isfile(destination):
|
||||
trailer_file = fireEvent('file.download', url = trailer, dest = destination, urlopen_kwargs = {'headers': {'User-Agent': 'Quicktime'}}, single = True)
|
||||
if os.path.getsize(trailer_file) < (1024 * 1024): # Don't trust small trailers (1MB), try next one
|
||||
if trailer_file and os.path.getsize(trailer_file) < (1024 * 1024): # Don't trust small trailers (1MB), try next one
|
||||
os.unlink(trailer_file)
|
||||
continue
|
||||
else:
|
||||
|
||||
+10
-1
@@ -17,7 +17,7 @@ from couchpotato import KeyHandler, LoginHandler, LogoutHandler
|
||||
from couchpotato.api import NonBlockHandler, ApiHandler
|
||||
from couchpotato.core.event import fireEventAsync, fireEvent
|
||||
from couchpotato.core.helpers.encoding import sp
|
||||
from couchpotato.core.helpers.variable import getDataDir, tryInt
|
||||
from couchpotato.core.helpers.variable import getDataDir, tryInt, getFreeSpace
|
||||
import requests
|
||||
from tornado.httpserver import HTTPServer
|
||||
from tornado.web import Application, StaticFileHandler, RedirectHandler
|
||||
@@ -195,6 +195,15 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
|
||||
log = CPLog(__name__)
|
||||
log.debug('Started with options %s', options)
|
||||
|
||||
# Check available space
|
||||
try:
|
||||
total_space, available_space = getFreeSpace(data_dir)
|
||||
if available_space < 100:
|
||||
log.error('Shutting down as CP needs some space to work. You\'ll get corrupted data otherwise. Only %sMB left', available_space)
|
||||
return
|
||||
except:
|
||||
log.error('Failed getting diskspace: %s', traceback.format_exc())
|
||||
|
||||
def customwarn(message, category, filename, lineno, file = None, line = None):
|
||||
log.warning('%s %s %s line:%s', (category, message, filename, lineno))
|
||||
warnings.showwarning = customwarn
|
||||
|
||||
Reference in New Issue
Block a user