Completed
Push — dev-4.1-unstable ( 0d60bb...136355 )
by Felipe A.
01:25
created

sort()   A

Complexity

Conditions 3

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
dl 0
loc 18
rs 9.4285
c 0
b 0
f 0
1
#!/usr/bin/env python
2
# -*- coding: UTF-8 -*-
3
4
import logging
5
import os
6
import os.path
7
import json
8
import base64
9
10
from flask import Flask, Response, request, render_template, redirect, \
11
                  url_for, send_from_directory, stream_with_context, \
12
                  make_response
13
from werkzeug.exceptions import NotFound
14
15
from .__meta__ import __app__, __version__, __license__, __author__  # noqa
16
from .manager import PluginManager
17
from .file import Node, OutsideRemovableBase, OutsideDirectoryBase, \
18
                  secure_filename
19
from . import compat
20
21
__basedir__ = os.path.abspath(os.path.dirname(compat.fsdecode(__file__)))
22
23
logger = logging.getLogger(__name__)
24
25
app = Flask(
26
    __name__,
27
    static_url_path='/static',
28
    static_folder=os.path.join(__basedir__, "static"),
29
    template_folder=os.path.join(__basedir__, "templates")
30
    )
31
app.config.update(
32
    directory_base=compat.getcwd(),
33
    directory_start=compat.getcwd(),
34
    directory_remove=None,
35
    directory_upload=None,
36
    directory_tar_buffsize=262144,
37
    directory_downloadable=True,
38
    use_binary_multiples=True,
39
    plugin_modules=[],
40
    plugin_namespaces=(
41
        'browsepy.plugin',
42
        'browsepy_',
43
        '',
44
        ),
45
    )
46
47
if "BROWSEPY_SETTINGS" in os.environ:
48
    app.config.from_envvar("BROWSEPY_SETTINGS")
49
50
plugin_manager = PluginManager(app)
51
52
53
def cookie_browse_sorting():
54
    '''
55
    Get sorting-cookie data of current request.
56
57
    :returns: sorting-cookie data as dict
58
    :rtype: dict
59
    '''
60
    try:
61
        data = request.cookies.get('browse-sorting', 'e30=').encode('ascii')
62
        return json.loads(base64.b64decode(data).decode('utf-8'))
63
    except (ValueError, TypeError, KeyError) as e:
64
        print(e)
65
        return {}
66
67
68
def browse_sortkey_reverse(prop):
69
    '''
70
    Get sorting function for browse
71
72
    :returns: tuple with sorting gunction and reverse bool
73
    :rtype: tuple of a dict and a bool
74
    '''
75
    if prop.startswith('-'):
76
        prop = prop[1:]
77
        reverse = True
78
    elif prop.startswith('+'):
79
        prop = prop[1:]
80
        reverse = False
81
    else:
82
        reverse = False
83
84
    if prop == 'text':
85
        return (
86
            lambda x: (
87
                x.is_directory == reverse,
88
                x.default_action[1].text.lower()
89
                ),
90
            reverse
91
            )
92
    if prop == 'size':
93
        return (
94
            lambda x: (
95
                x.is_directory == reverse,
96
                x.stats.st_size
97
                ),
98
            reverse
99
            )
100
    return (
101
        lambda x: (
102
            x.is_directory == reverse,
103
            getattr(x, prop, None)
104
            ),
105
        reverse
106
        )
107
108
109
def stream_template(template_name, **context):
110
    '''
111
    Some templates can be huge, this function returns an streaming response,
112
    sending the content in chunks and preventing from timeout.
113
114
    :param template_name: template
115
    :param **context: parameters for templates.
116
    :yields: HTML strings
117
    '''
118
    app.update_template_context(context)
119
    template = app.jinja_env.get_template(template_name)
120
    stream = template.generate(context)
121
    return Response(stream_with_context(stream))
122
123
124
@app.context_processor
125
def template_globals():
126
    return {
127
        'manager': app.extensions['plugin_manager'],
128
        'len': len,
129
        }
130
131
132
@app.route('/sort/<string:property>', defaults={"path": ""})
133
@app.route('/sort/<string:property>/<path:path>')
134
def sort(property, path):
135
    try:
136
        directory = Node.from_urlpath(path)
137
    except OutsideDirectoryBase:
138
        return NotFound()
139
140
    if not directory.is_directory:
141
        return NotFound()
142
143
    data = cookie_browse_sorting()
144
    data[path] = property
145
    raw_data = json.dumps(data).encode('utf-8')
146
147
    response = redirect(url_for(".browse", path=directory.urlpath))
148
    response.set_cookie('browse-sorting', base64.b64encode(raw_data))
149
    return response
150
151
152
@app.route("/browse", defaults={"path": ""})
153
@app.route('/browse/<path:path>')
154
def browse(path):
155
    sort_property = cookie_browse_sorting().get(path, 'text')
156
    sort_fnc, sort_reverse = browse_sortkey_reverse(sort_property)
157
158
    try:
159
        directory = Node.from_urlpath(path)
160
        if directory.is_directory:
161
            return stream_template(
162
                'browse.html',
163
                file=directory,
164
                sort_property=sort_property,
165
                sort_fnc=sort_fnc,
166
                sort_reverse=sort_reverse
167
                )
168
    except OutsideDirectoryBase:
169
        pass
170
    return NotFound()
171
172
173
@app.route('/open/<path:path>', endpoint="open")
174
def open_file(path):
175
    try:
176
        file = Node.from_urlpath(path)
177
        if file.is_file:
178
            return send_from_directory(file.parent.path, file.name)
179
    except OutsideDirectoryBase:
180
        pass
181
    return NotFound()
182
183
184
@app.route("/download/file/<path:path>")
185
def download_file(path):
186
    try:
187
        file = Node.from_urlpath(path)
188
        if file.is_file:
189
            return file.download()
190
    except OutsideDirectoryBase:
191
        pass
192
    return NotFound()
193
194
195
@app.route("/download/directory/<path:path>.tgz")
196
def download_directory(path):
197
    try:
198
        directory = Node.from_urlpath(path)
199
        if directory.is_directory:
200
            return directory.download()
201
    except OutsideDirectoryBase:
202
        pass
203
    return NotFound()
204
205
206
@app.route("/remove/<path:path>", methods=("GET", "POST"))
207
def remove(path):
208
    try:
209
        file = Node.from_urlpath(path)
210
    except OutsideDirectoryBase:
211
        return NotFound()
212
    if request.method == 'GET':
213
        if not file.can_remove:
214
            return NotFound()
215
        return render_template('remove.html', file=file)
216
    parent = file.parent
217
    if parent is None:
218
        # base is not removable
219
        return NotFound()
220
221
    try:
222
        file.remove()
223
    except OutsideRemovableBase:
224
        return NotFound()
225
226
    return redirect(url_for(".browse", path=parent.urlpath))
227
228
229
@app.route("/upload", defaults={'path': ''}, methods=("POST",))
230
@app.route("/upload/<path:path>", methods=("POST",))
231
def upload(path):
232
    try:
233
        directory = Node.from_urlpath(path)
234
    except OutsideDirectoryBase:
235
        return NotFound()
236
237
    if not directory.is_directory or not directory.can_upload:
238
        return NotFound()
239
240
    for f in request.files.values():
241
        filename = secure_filename(f.filename)
242
        if filename:
243
            filename = directory.choose_filename(filename)
244
            filepath = os.path.join(directory.path, filename)
245
            f.save(filepath)
246
    return redirect(url_for(".browse", path=directory.urlpath))
247
248
249
@app.route("/")
250
def index():
251
    path = app.config["directory_start"] or app.config["directory_base"]
252
    try:
253
        urlpath = Node(path).urlpath
254
    except OutsideDirectoryBase:
255
        return NotFound()
256
    return browse(urlpath)
257
258
259
@app.after_request
260
def page_not_found(response):
261
    if response.status_code == 404:
262
        return make_response((render_template('404.html'), 404))
263
    return response
264
265
266
@app.errorhandler(404)
267
def page_not_found_error(e):
268
    return render_template('404.html'), 404
269
270
271
@app.errorhandler(500)
272
def internal_server_error(e):  # pragma: no cover
273
    logger.exception(e)
274
    return getattr(e, 'message', 'Internal server error'), 500
275