Completed
Push — file-actions ( 3ab188...6cd94b )
by Felipe A.
28s
created

selection()   F

Complexity

Conditions 9

Size

Total Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
c 0
b 0
f 0
dl 0
loc 40
rs 3
1
#!/usr/bin/env python
2
# -*- coding: UTF-8 -*-
3
4
import os
5
import os.path
6
import shutil
7
8
from flask import Blueprint, render_template, request, redirect, url_for, \
9
                  make_response
10
from werkzeug.exceptions import NotFound
11
12
from browsepy import get_cookie_browse_sorting, browse_sortkey_reverse
13
from browsepy.file import Node, abspath_to_urlpath, secure_filename, \
14
                          current_restricted_chars, common_path_separators
15
from browsepy.compat import map, re_escape, FileNotFoundError
16
from browsepy.exceptions import OutsideDirectoryBase
17
18
from .clipboard import Clipboard
19
from .exceptions import FileActionsException, ClipboardException, \
20
                        InvalidClipboardModeError, InvalidClipboardItemError, \
21
                        MissingClipboardItemError, InvalidDirnameError
22
23
24
__basedir__ = os.path.dirname(os.path.abspath(__file__))
25
26
actions = Blueprint(
27
    'file_actions',
28
    __name__,
29
    url_prefix='/file-actions',
30
    template_folder=os.path.join(__basedir__, 'templates'),
31
    static_folder=os.path.join(__basedir__, 'static'),
32
    )
33
re_basename = '^[^ {0}]([^{0}]*[^ {0}])?$'.format(
34
    re_escape(current_restricted_chars + common_path_separators)
35
    )
36
37
38
@actions.route('/create/directory', methods=('GET', 'POST'),
39
               defaults={'path': ''})
40
@actions.route('/create/directory/<path:path>', methods=('GET', 'POST'))
41
def create_directory(path):
42
    try:
43
        directory = Node.from_urlpath(path)
44
    except OutsideDirectoryBase:
45
        return NotFound()
46
47
    if not directory.is_directory or not directory.can_upload:
48
        return NotFound()
49
50
    if request.method == 'GET':
51
        return render_template(
52
            'create_directory.file_actions.html',
53
            file=directory,
54
            re_basename=re_basename,
55
            )
56
57
    basename = request.form['name']
58
    if secure_filename(basename) != basename or not basename:
59
        raise InvalidDirnameError(
60
            path=directory.path,
61
            name=basename,
62
            )
63
64
    os.mkdir(os.path.join(directory.path, basename))
65
66
    return redirect(url_for('browse', path=directory.urlpath))
67
68
69
@actions.route('/selection', methods=('GET', 'POST'), defaults={'path': ''})
70
@actions.route('/selection/<path:path>', methods=('GET', 'POST'))
71
def selection(path):
72
    sort_property = get_cookie_browse_sorting(path, 'text')
73
    sort_fnc, sort_reverse = browse_sortkey_reverse(sort_property)
74
75
    try:
76
        directory = Node.from_urlpath(path)
77
    except OutsideDirectoryBase:
78
        return NotFound()
79
80
    if directory.is_excluded or not directory.is_directory:
81
        return NotFound()
82
83
    if request.method == 'POST':
84
        action_fmt = 'action-{}'.format
85
        mode = None
86
        for action in ('cut', 'copy'):
87
            if request.form.get(action_fmt(action)):
88
                mode = action
89
                break
90
91
        if mode in ('cut', 'copy'):
92
            response = redirect(url_for('browse', path=directory.urlpath))
93
            clipboard = Clipboard(request.form.getlist('path'), mode)
94
            clipboard.to_response(response)
95
            return response
96
97
        return redirect(request.path)
98
99
    clipboard = Clipboard.from_request()
100
    clipboard.mode = 'select'  # disables exclusion
101
    return render_template(
102
        'selection.file_actions.html',
103
        file=directory,
104
        clipboard=clipboard,
105
        cut_support=any(node.can_remove for node in directory.listdir()),
106
        sort_property=sort_property,
107
        sort_fnc=sort_fnc,
108
        sort_reverse=sort_reverse,
109
        )
110
111
112
@actions.route('/clipboard/paste', defaults={'path': ''})
113
@actions.route('/clipboard/paste/<path:path>')
114
def clipboard_paste(path):
115
116
    def copy(target, node, join_fnc=os.path.join):
117
        dest = join_fnc(
118
            target.path,
119
            target.choose_filename(node.name)
120
            )
121
        if node.is_directory:
122
            shutil.copytree(node.path, dest)
123
        else:
124
            shutil.copy2(node.path, dest)
125
126
    def cut(target, node, join_fnc=os.path.join):
127
        if node.parent.path != target.path:
128
            dest = join_fnc(
129
                target.path,
130
                target.choose_filename(node.name)
131
                )
132
            shutil.move(node.path, dest)
133
134
    try:
135
        directory = Node.from_urlpath(path)
136
    except OutsideDirectoryBase:
137
        return NotFound()
138
139
    if (
140
      not directory.is_directory or
141
      not directory.can_upload or
142
      directory.is_excluded
143
      ):
144
        return NotFound()
145
146
    response = redirect(url_for('browse', path=directory.urlpath))
147
    clipboard = Clipboard.from_request()
148
    mode = clipboard.mode
149
    clipboard.mode = 'paste'  # disables exclusion
150
151
    nodes = [node for node in map(Node.from_urlpath, clipboard)]
152
153
    if mode == 'cut':
154
        paste_fnc = cut
155
        for node in nodes:
156
            if node.is_excluded or not node.can_remove:
157
                raise InvalidClipboardItemError(
158
                    path=directory.path,
159
                    item=node.urlpath
160
                    )
161
    elif mode == 'copy':
162
        paste_fnc = copy
163
        for node in nodes:
164
            if node.is_excluded:
165
                raise InvalidClipboardItemError(
166
                    path=directory.path,
167
                    item=node.urlpath
168
                    )
169
    else:
170
        raise InvalidClipboardModeError(path=directory.path, mode=mode)
171
172
    for node in nodes:
173
        try:
174
            paste_fnc(directory, node)
175
        except FileNotFoundError:
176
            raise MissingClipboardItemError(
177
                path=directory.path,
178
                item=node.urlpath
179
                )
180
181
    clipboard.clear()
182
    clipboard.to_response(response)
183
    return response
184
185
186
@actions.route('/clipboard/clear', defaults={'path': ''})
187
@actions.route('/clipboard/clear/<path:path>')
188
def clipboard_clear(path):
189
    response = redirect(url_for('browse', path=path))
190
    clipboard = Clipboard.from_request()
191
    clipboard.clear()
192
    clipboard.to_response(response)
193
    return response
194
195
196
@actions.errorhandler(FileActionsException)
197
def clipboard_error(e):
198
    file = Node(e.path) if hasattr(e, 'path') else None
199
    item = Node.from_urlpath(e.item) if hasattr(e, 'item') else None
200
    response = make_response((
201
        render_template(
202
            '400.file_actions.html',
203
            error=e, file=file, item=item
204
            ),
205
        400
206
        ))
207
    if isinstance(e, ClipboardException):
208
        clipboard = Clipboard.from_request()
209
        clipboard.clear()
210
        clipboard.to_response(response)
211
    return response
212
213
214
def register_plugin(manager):
215
    '''
216
    Register blueprints and actions using given plugin manager.
217
218
    :param manager: plugin manager
219
    :type manager: browsepy.manager.PluginManager
220
    '''
221
    def detect_upload(directory):
222
        return directory.is_directory and directory.can_upload
223
224
    def detect_clipboard(directory):
225
        return directory.is_directory and Clipboard.from_request()
226
227
    def excluded_clipboard(path):
228
        clipboard = Clipboard.from_request(request)
229
        if clipboard.mode == 'cut':
230
            base = manager.app.config['directory_base']
231
            return abspath_to_urlpath(path, base) in clipboard
232
233
    manager.register_exclude_function(excluded_clipboard)
234
    manager.register_blueprint(actions)
235
    manager.register_widget(
236
        place='styles',
237
        type='stylesheet',
238
        endpoint='file_actions.static',
239
        filename='browse.css',
240
        filter=detect_clipboard,
241
        )
242
    manager.register_widget(
243
        place='header',
244
        type='button',
245
        endpoint='file_actions.create_directory',
246
        text='Create directory',
247
        filter=detect_upload,
248
        )
249
    manager.register_widget(
250
        place='header',
251
        type='button',
252
        endpoint='file_actions.selection',
253
        filter=lambda directory: directory.is_directory,
254
        text='Selection...',
255
        )
256
    manager.register_widget(
257
        place='header',
258
        type='html',
259
        html=lambda file: render_template(
260
            'widget.clipboard.file_actions.html',
261
            file=file,
262
            clipboard=Clipboard.from_request()
263
            ),
264
        filter=detect_clipboard,
265
        )
266