5
from flexget import validator
6
from flexget.plugin import register_plugin
7
from flexget.utils.template import RenderError
9
log = logging.getLogger('move')
12
def get_directory_size(directory):
14
:param directory: Path
15
:return: Size in bytes (recursively)
18
for (path, dirs, files) in os.walk(directory):
20
filename = os.path.join(path, file)
21
dir_size += os.path.getsize(filename)
25
class MovePlugin(object):
28
root = validator.factory()
29
root.accept('boolean')
30
config = root.accept('dict')
31
config.accept('path', key='to', allow_replacement=True)
32
config.accept('text', key='filename')
33
config.accept('boolean', key='unpack_safety')
34
#config.accept('list', key='move_with').accept('text') # TODO
35
config.accept('number', key='clean_source')
38
def on_feed_output(self, feed, config):
43
for entry in feed.accepted:
44
if not 'location' in entry:
45
log.warning('Cannot move `%s` because entry does not have location field.' % entry['title'])
49
src = entry['location']
50
if not os.path.exists(src):
51
log.warning('Cannot move `%s` because location `%s` does not exists (anymore)' % (entry['title'], src))
53
if not os.path.isfile(src):
54
log.warning('Cannot move `%s` because location `%s` is not a file' % (entry['title'], src))
58
filepath, filename = os.path.split(src)
59
# get proper value in order of: entry, config, above split
60
dst_path = entry.get('path', config.get('to', filepath))
62
if entry.get('filename') and entry['filename'] != filename:
63
# entry specifies different filename than what was split from the path
64
# since some inputs fill in filename it must be different in order to be used
65
dst_filename = entry['filename']
66
elif 'filename' in config:
67
# use from configuration if given
68
dst_filename = config['filename']
70
# just use original filename
71
dst_filename = filename
74
dst_path = entry.render(dst_path)
76
log.error('Path value replacement `%s` failed for `%s`' % (dst_path, entry['title']))
79
dst_filename = entry.render(dst_filename)
81
log.error('Filename value replacement `%s` failed for `%s`' % (dst_filename, entry['title']))
84
dst = os.path.join(dst_path, dst_filename)
85
if dst == entry['location']:
86
log.info('Not moving %s because source and destination are the same.' % dst)
89
if not os.path.exists(dst_path):
90
if feed.manager.options.test:
91
log.info('Would create `%s`' % dst_path)
93
log.info('Creating destination directory `%s`' % dst_path)
95
if not os.path.isdir(dst_path) and not feed.manager.options.test:
96
log.warning('Cannot move `%s` because destination `%s` is not a directory' % (entry['title'], dst_path))
100
log.verbose('Source and destination are same, skipping `%s`' % entry['title'])
104
if config.get('unpack_safety', entry.get('unpack_safety', True)):
108
feed.fail(entry, 'Move has been waiting unpacking for 30 minutes')
110
size = os.path.getsize(src)
112
new_size = os.path.getsize(src)
115
log.verbose('File `%s` is possibly being unpacked, waiting ...' % filename)
121
if feed.manager.options.test:
122
log.info('Would move `%s` to `%s`' % (src, dst))
124
log.info('Moving `%s` to `%s`' % (src, dst))
125
shutil.move(src, dst)
126
if 'clean_source' in config:
127
if not os.path.isdir(src):
128
base_path = os.path.split(src)[0]
129
size = get_directory_size(base_path) / 1024 / 1024
130
log.debug('base_path: %s size: %s' % (base_path, size))
131
if size <= config['clean_source']:
132
if feed.manager.options.test:
133
log.info('Would delete %s and everything under it' % base_path)
135
log.info('Deleting `%s`' % base_path)
136
shutil.rmtree(base_path, ignore_errors=True)
139
'Path `%s` left because it exceeds safety value set in clean_source option' % base_path)
141
log.verbose('Cannot clean_source `%s` because source is a directory' % src)
144
register_plugin(MovePlugin, 'move', api_ver=2)