Completed
Push — file-actions ( eff3c9...3ab188 )
by Felipe A.
02:39
created

detect_selection()   A

Complexity

Conditions 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 1
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
from werkzeug.exceptions import NotFound
10
11
from browsepy import get_cookie_browse_sorting, browse_sortkey_reverse
12
from browsepy.file import Node, abspath_to_urlpath, secure_filename, \
13
                          current_restricted_chars, common_path_separators
14
from browsepy.compat import map, re_escape
15
from browsepy.exceptions import OutsideDirectoryBase, InvalidFilenameError
16
17
from .clipboard import Clipboard
18
19
20
__basedir__ = os.path.dirname(os.path.abspath(__file__))
21
22
actions = Blueprint(
23
    'file_actions',
24
    __name__,
25
    url_prefix='/file-actions',
26
    template_folder=os.path.join(__basedir__, 'templates'),
27
    static_folder=os.path.join(__basedir__, 'static'),
28
    )
29
re_basename = '^[^ {0}]([^{0}]*[^ {0}])?$'.format(
30
    re_escape(current_restricted_chars + common_path_separators)
31
    )
32
33
34
@actions.route('/create/directory', methods=('GET', 'POST'),
35
               defaults={'path': ''})
36
@actions.route('/create/directory/<path:path>', methods=('GET', 'POST'))
37
def create_directory(path):
38
    try:
39
        directory = Node.from_urlpath(path)
40
    except OutsideDirectoryBase:
41
        return NotFound()
42
43
    if not directory.is_directory or not directory.can_upload:
44
        return NotFound()
45
46
    if request.method == 'GET':
47
        return render_template(
48
            'create_directory.file_actions.html',
49
            file=directory,
50
            re_basename=re_basename,
51
            )
52
53
    basename = request.form['name']
54
    if secure_filename(basename) != basename or not basename:
55
        raise InvalidFilenameError(
56
            path=directory.path,
57
            filename=basename,
58
            )
59
60
    os.mkdir(os.path.join(directory.path, basename))
61
62
    return redirect(url_for('browse', file=directory))
63
64
65
@actions.route('/clipboard', methods=('GET', 'POST'), defaults={'path': ''})
66
@actions.route('/clipboard/<path:path>', methods=('GET', 'POST'))
67
def clipboard(path):
68
    sort_property = get_cookie_browse_sorting(path, 'text')
69
    sort_fnc, sort_reverse = browse_sortkey_reverse(sort_property)
70
71
    try:
72
        directory = Node.from_urlpath(path)
73
    except OutsideDirectoryBase:
74
        return NotFound()
75
76
    if directory.is_excluded or not directory.is_directory:
77
        return NotFound()
78
79
    if request.method == 'POST':
80
        mode = 'cut' if request.form.get('mode-cut') else 'copy'
81
        response = redirect(url_for('browse', path=directory.urlpath))
82
        clipboard = Clipboard(request.form.getlist('path'), mode)
83
        clipboard.to_response(response)
84
        return response
85
86
    clipboard = Clipboard.from_request()
87
    clipboard.mode = 'select'  # disables exclusion
88
    return render_template(
89
        'clipboard.file_actions.html',
90
        file=directory,
91
        clipboard=clipboard,
92
        cut_support=any(node.can_remove for node in directory.listdir()),
93
        sort_property=sort_property,
94
        sort_fnc=sort_fnc,
95
        sort_reverse=sort_reverse,
96
        )
97
98
99
@actions.route('/clipboard/paste', defaults={'path': ''})
100
@actions.route('/clipboard/paste/<path:path>')
101
def clipboard_paste(path):
102
    try:
103
        directory = Node.from_urlpath(path)
104
    except OutsideDirectoryBase:
105
        return NotFound()
106
107
    if (
108
      not directory.is_directory or
109
      directory.is_excluded or
110
      not directory.can_upload
111
      ):
112
        return NotFound()
113
114
    response = redirect(url_for('browse', path=directory.urlpath))
115
    clipboard = Clipboard.from_request()
116
    cut = clipboard.mode == 'cut'
117
    clipboard.mode = 'paste'  # disables exclusion
118
119
    for node in map(Node.from_urlpath, clipboard):
120
        if not node.is_excluded:
121
            if not cut:
122
                if node.is_directory:
123
                    shutil.copytree(node.path, directory.path)
124
                else:
125
                    shutil.copy2(node.path, directory.path)
126
            elif node.parent.can_remove:
127
                shutil.move(node.path, directory.path)
128
129
    clipboard.clear()
130
    clipboard.to_response(response)
131
    return response
132
133
134
@actions.route('/clipboard/clear', defaults={'path': ''})
135
@actions.route('/clipboard/clear/<path:path>')
136
def clipboard_clear(path):
137
    response = redirect(url_for('browse', path=path))
138
    clipboard = Clipboard.from_request()
139
    clipboard.clear()
140
    clipboard.to_response(response)
141
    return response
142
143
144
def register_plugin(manager):
145
    '''
146
    Register blueprints and actions using given plugin manager.
147
148
    :param manager: plugin manager
149
    :type manager: browsepy.manager.PluginManager
150
    '''
151
    def detect_selection(directory):
152
        return (
153
            directory.is_directory and
154
            Clipboard.from_request().mode == 'select'
155
            )
156
157
    def detect_upload(directory):
158
        return directory.is_directory and directory.can_upload
159
160
    def detect_target(directory):
161
        return detect_upload(directory) and detect_clipboard(directory)
162
163
    def detect_clipboard(directory):
164
        return directory.is_directory and Clipboard.from_request()
165
166
    def excluded_clipboard(path):
167
        clipboard = Clipboard.from_request(request)
168
        if clipboard.mode == 'cut':
169
            base = manager.app.config['directory_base']
170
            return abspath_to_urlpath(path, base) in clipboard
171
172
    excluded = manager.app.config.get('exclude_fnc')
173
    manager.app.config['exclude_fnc'] = (
174
        excluded_clipboard
175
        if not excluded else
176
        lambda path: excluded_clipboard(path) or excluded(path)
177
        )
178
    manager.register_blueprint(actions)
179
    manager.register_widget(
180
        place='styles',
181
        type='stylesheet',
182
        endpoint='file_actions.static',
183
        filename='style.css',
184
        filter=detect_selection,
185
        )
186
    manager.register_widget(
187
        place='scripts',
188
        type='script',
189
        endpoint='file_actions.static',
190
        filename='script.js',
191
        filter=detect_selection,
192
        )
193
    manager.register_widget(
194
        place='header',
195
        type='button',
196
        endpoint='file_actions.create_directory',
197
        text='Create directory',
198
        filter=detect_upload,
199
        )
200
    manager.register_widget(
201
        place='header',
202
        type='button',
203
        endpoint='file_actions.clipboard',
204
        filter=lambda directory: directory.is_directory,
205
        text=lambda directory: (
206
            'Selection ({})...'.format(Clipboard.count())
207
            if Clipboard.count() else
208
            'Selection...'
209
            ),
210
        )
211
    manager.register_widget(
212
        place='header',
213
        type='button',
214
        endpoint='file_actions.clipboard_paste',
215
        text='Paste here',
216
        filter=detect_target,
217
        )
218
    manager.register_widget(
219
        place='header',
220
        type='button',
221
        endpoint='file_actions.clipboard_clear',
222
        text='Clear',
223
        filter=detect_clipboard,
224
        )
225