Completed
Push — master ( 664f3c...5c8ee4 )
by Felipe A.
59s
created

browsepy.abspath_to_urlpath()   A

Complexity

Conditions 1

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 1
dl 0
loc 12
rs 9.4286
1
#!/usr/bin/env python
2
# -*- coding: UTF-8 -*-
3
4
import re
5
import os
6
import os.path
7
import itertools
8
9
from flask import Flask, Response, request, render_template, redirect, \
10
                  url_for, send_from_directory, stream_with_context, \
11
                  make_response
12
from werkzeug.exceptions import NotFound
13
14
from .__meta__ import __app__, __version__, __license__, __author__
15
from .managers import PluginManager
16
from .file import File, TarFileStream, \
17
                  OutsideRemovableBase, OutsideDirectoryBase, \
18
                  relativize_path, secure_filename, fs_encoding
19
from .compat import PY_LEGACY, range
20
21
__basedir__ = os.path.abspath(os.path.dirname(__file__))
22
23
app = Flask(__name__,
24
    static_url_path = '/static',
25
    static_folder = os.path.join(__basedir__, "static"),
26
    template_folder = os.path.join(__basedir__, "templates")
27
    )
28
app.config.update(
29
    directory_base = os.path.abspath(os.getcwd()),
30
    directory_start = os.path.abspath(os.getcwd()),
31
    directory_remove = None,
32
    directory_upload = None,
33
    directory_tar_buffsize = 262144,
34
    directory_downloadable = True,
35
    use_binary_multiples = True,
36
    plugin_modules = [],
37
    plugin_namespaces = (
38
        'browsepy.plugin',
39
        '',
40
        ),
41
    )
42
43
if "BROWSEPY_SETTINGS" in os.environ:
44
    app.config.from_envvar("BROWSEPY_SETTINGS")
45
46
plugin_manager = PluginManager(app)
47
48
def empty_iterable(iterable):
49
    '''
50
    Get if iterable is empty, and return a new iterable.
51
52
    :param iterable: iterable
53
    :return: whether iterable is empty or not, and iterable
54
    :rtype: tuple of bool and iterable
55
    '''
56
    try:
57
        rest = iter(iterable)
58
        first = next(rest)
59
        return False, itertools.chain((first,), rest)
60
    except StopIteration:
61
        return True, iter(())
62
63
def stream_template(template_name, **context):
64
    '''
65
    Some templates can be huge, this function returns an streaming response,
66
    sending the content in chunks and preventing from timeout.
67
68
    :param template_name: template
69
    :param **context: parameters for templates.
70
    :yields: HTML strings
71
    '''
72
    app.update_template_context(context)
73
    template = app.jinja_env.get_template(template_name)
74
    stream = template.generate(context)
75
    return Response(stream_with_context(stream))
76
77
@app.before_first_request
78
def finish_initialization():
79
    plugin_manager = app.extensions['plugin_manager']
80
    for plugin in app.config['plugin_modules']:
81
        plugin_manager.load_plugin(plugin)
82
83
@app.context_processor
84
def template_globals():
85
    return {
86
        'len': len,
87
        }
88
89
@app.route("/browse", defaults={"path":""})
90
@app.route('/browse/<path:path>')
91
def browse(path):
92
    try:
93
        directory = File.from_urlpath(path)
94
        if directory.is_directory:
95
            files = directory.listdir()
96
            empty_files, files = empty_iterable(files)
97
            return stream_template("browse.html", file=directory)
98
    except OutsideDirectoryBase:
99
        pass
100
    return NotFound()
101
102
@app.route('/open/<path:path>', endpoint="open")
103
def open_file(path):
104
    try:
105
        file = File.from_urlpath(path)
106
        if file.is_file:
107
            return send_from_directory(file.parent.path, file.name)
108
    except OutsideDirectoryBase:
109
        pass
110
    return NotFound()
111
112
@app.route("/download/file/<path:path>")
113
def download_file(path):
114
    try:
115
        file = File.from_urlpath(path)
116
        if file.is_file:
117
            return file.download()
118
    except OutsideDirectoryBase:
119
        pass
120
    return NotFound()
121
122
@app.route("/download/directory/<path:path>.tgz")
123
def download_directory(path):
124
    try:
125
        directory = File.from_urlpath(path)
126
        if directory.is_directory:
127
            return directory.download()
128
    except OutsideDirectoryBase:
129
        pass
130
    return NotFound()
131
132
@app.route("/remove/<path:path>", methods=("GET", "POST"))
133
def remove(path):
134
    try:
135
        file = File.from_urlpath(path)
136
    except OutsideDirectoryBase:
137
        return NotFound()
138
    if request.method == 'GET':
139
        if not file.can_remove:
140
            return NotFound()
141
        return render_template('remove.html', file=file)
142
    parent = file.parent
143
    if parent is None:
144
        # base is not removable
145
        return NotFound()
146
147
    try:
148
        file.remove()
149
    except OutsideRemovableBase:
150
        return NotFound()
151
152
    return redirect(url_for(".browse", path=parent.urlpath))
153
154
@app.route("/upload", defaults={'path': ''}, methods=("POST",))
155
@app.route("/upload/<path:path>", methods=("POST",))
156
def upload(path):
157
    try:
158
        directory = File.from_urlpath(path)
159
    except OutsideDirectoryBase:
160
        return NotFound()
161
162
    if not directory.is_directory or not directory.can_upload:
163
        return NotFound()
164
165
    for f in request.files.values():
166
        filename = secure_filename(f.filename)
167
        if filename:
168
            definitive_filename = directory.choose_filename(filename)
169
            f.save(os.path.join(directory.path, definitive_filename))
170
    return redirect(url_for(".browse", path=directory.urlpath))
171
172
@app.route("/")
173
def index():
174
    path = app.config["directory_start"] or app.config["directory_base"]
175
    if PY_LEGACY and not isinstance(path, unicode):
176
        path = path.decode(fs_encoding)
177
    try:
178
        urlpath = File(path).urlpath
179
    except OutsideDirectoryBase:
180
        return NotFound()
181
    return browse(urlpath)
182
183
@app.after_request
184
def page_not_found(response):
185
    if response.status_code == 404:
186
        return make_response((render_template('404.html'), 404))
187
    return response
188
189
@app.errorhandler(404)
190
def page_not_found(e):
191
    return render_template('404.html'), 404
192
193
@app.errorhandler(500)
194
def internal_server_error(e): # pragma: no cover
195
    import traceback
196
    traceback.print_exc()
197
    return getattr(e, 'message', 'Internal server error'), 500
198