Skip to content

Commit b80726f

Browse files
Johnetordoffcslzchen
authored andcommitted
Add hierarchical zip renderer and adjust test
1 parent fc0d556 commit b80726f

File tree

3 files changed

+101
-57
lines changed

3 files changed

+101
-57
lines changed

mfr/extensions/zip/render.py

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import os
22
import zipfile
33

4-
import markupsafe
54
from mako.lookup import TemplateLookup
65

76
from mfr.core import extension
@@ -17,15 +16,55 @@ class ZipRenderer(extension.BaseRenderer):
1716

1817
def render(self):
1918
zip_file = zipfile.ZipFile(self.file_path, 'r')
19+
files = [file for file in zip_file.filelist if not file.filename.startswith('__MACOSX')]
2020

21-
filelist = [{'name': markupsafe.escape(file.filename),
22-
'size': sizeof_fmt(int(file.file_size)),
23-
'date': "%d-%02d-%02d %02d:%02d:%02d" % file.date_time[:6]} for file in zip_file.filelist
24-
if not file.filename.startswith('__MACOSX')]
21+
data = self.filelist_to_tree(files)
2522

26-
message = '' if filelist else 'This zip file is empty.'
23+
return self.TEMPLATE.render(data=data, base=self.assets_url)
2724

28-
return self.TEMPLATE.render(zipped_filenames=filelist, message=message)
25+
def filelist_to_tree(self, files):
26+
27+
self.icons_url = self.assets_url + '/img'
28+
29+
tree_data = [{
30+
'text': self.metadata.name + self.metadata.ext,
31+
'icon': self.icons_url + '/file_extension_zip.png',
32+
'children': []
33+
}]
34+
35+
for file in files:
36+
node_path = tree_data[0]
37+
paths = [path for path in file.filename.split('/') if path]
38+
for path in paths:
39+
if not len(node_path['children']) or node_path['children'][-1]['text'] != path:
40+
# Add a child
41+
new_node = {'text': path, 'children': []}
42+
43+
if new_node['text']: # If not a placeholder/"root" directory.
44+
date = '%d-%02d-%02d %02d:%02d:%02d' % file.date_time[:6]
45+
size = sizeof_fmt(int(file.file_size)) if file.file_size else ''
46+
47+
# create new node
48+
new_node['data'] = {'date': date, 'size': size}
49+
50+
if file.filename[-1] == '/':
51+
new_node['icon'] = self.icons_url + '/folder.png'
52+
else:
53+
ext = os.path.splitext(file.filename)[1].lstrip('.')
54+
if check_icon_ext(ext):
55+
new_node['icon'] = \
56+
self.icons_url + '/file_extension_{}.png'.format(ext)
57+
else:
58+
new_node['icon'] = self.icons_url + '/generic-file.png'
59+
60+
node_path['children'].append(new_node)
61+
62+
node_path = new_node
63+
else:
64+
# "go deeper" to get children of children.
65+
node_path = node_path['children'][-1]
66+
67+
return tree_data
2968

3069
@property
3170
def file_required(self):
@@ -34,3 +73,7 @@ def file_required(self):
3473
@property
3574
def cache_result(self):
3675
return True
76+
77+
def check_icon_ext(ext):
78+
return os.path.isfile(os.path.join(os.path.dirname(__file__), 'static', 'img', 'icons',
79+
'file_extension_{}.png'.format(ext)))
Lines changed: 38 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,43 @@
1-
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,600,300,700" rel="stylesheet" type="text/css">
2-
<link rel="stylesheet" href="static/css/bootstrap.min.css">
31
<link rel="stylesheet" href="static/css/default.css">
4-
<div style="word-wrap: break-word;" class="mfrViewer">
5-
<h1>
6-
Zip File:
7-
</h1>
8-
<p>
9-
Download the .zip file to view the contents.
10-
</p>
11-
${message}
12-
<table class="table table-hover">
13-
<thead>
14-
<th>File Name</th>
15-
<th>Modified</th>
16-
<th>Size</th>
17-
</thead>
18-
<tbody>
19-
% for file in zipped_filenames:
20-
<tr>
21-
<td>${file['name']}</td>
22-
<td>${file['date']}</td>
23-
<td>${file['size']}<td>
24-
</tr>
25-
% endfor
26-
</tbody>
27-
</table>
2+
<link rel="stylesheet" href="${base}/jstree-theme/style.css"/>
3+
4+
<script src="/static/js/jquery-1.11.3.min.js"></script>
5+
<script src="/static/js/bootstrap.min.js"></script>
6+
7+
<script src="${base}/js/jstree.min.js"></script>
8+
<script src="${base}/js/jstreetable.js"></script>
9+
10+
<div style="height: 500px;" class="mfrViewer">
11+
<input id="ziptree_search" class="form-control" style="margin-bottom: 10px;" type="text" placeholder="Search...">
12+
<div id="ziptree"></div>
2813
</div>
2914

15+
<script>
16+
$('#ziptree').jstree({
17+
"core" : {
18+
"data" : ${data},
19+
},
20+
"plugins" : [
21+
"table",
22+
"search"
23+
],
24+
'table': {
25+
'columns': [
26+
{'header': "Name", 'width': '100%'}, // these widths are strange but work.
27+
{'header': "Size", 'width': 80, 'value': "size"},
28+
{'header': "Date", 'width': 150, 'value': "date"}
29+
],
30+
}
31+
});
32+
var to = false;
33+
$('#ziptree_search').keyup(function () {
34+
if(to) { clearTimeout(to); }
35+
to = setTimeout(function () {
36+
var v = $('#ziptree_search').val();
37+
$('#ziptree').jstree(true).search(v);
38+
}, 250);
39+
});
40+
</script>
41+
3042
<script src="/static/js/mfr.js"></script>
3143
<script src="/static/js/mfr.child.js"></script>

tests/extensions/zip/test_renderer.py

Lines changed: 13 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import os
22
import re
3+
import json
34
from zipfile import ZipFile
45

56
import pytest
6-
from bs4 import BeautifulSoup
77

88
from mfr.core.provider import ProviderMetadata
99
from mfr.extensions.zip import ZipRenderer
@@ -24,6 +24,10 @@ def metadata():
2424
def zip_file():
2525
return ZipFile(os.path.join(BASE, 'files', 'test.zip'), 'r')
2626

27+
@pytest.fixture
28+
def zip_file_tree():
29+
return ZipFile(os.path.join(BASE, 'files', 'test-tree.zip'), 'r')
30+
2731

2832
@pytest.fixture
2933
def zip_empty_file():
@@ -55,36 +59,21 @@ def renderer(metadata, test_file_path, url, assets_url, export_url):
5559
return ZipRenderer(metadata, test_file_path, url, assets_url, export_url)
5660

5761

58-
59-
def remove_whitespace(str):
60-
str = re.sub('\n*', '', str)
61-
return re.sub(r'\ {2,}', '', str)
62+
@pytest.fixture
63+
def file_tree():
64+
with open(os.path.join(os.path.dirname(__file__), 'fixtures/fixtures.json'), 'r') as fp:
65+
return json.load(fp)['file_tree']
6266

6367

6468
class TestZipRenderer:
6569

6670
def test_render(self, renderer):
6771
body = renderer.render()
68-
parsed_html = BeautifulSoup(body)
69-
rows = parsed_html.findChildren('table')[0].findChildren(['tr'])
70-
71-
name = rows[2].findChildren('td')[0].get_text().strip()
72-
assert 'test/test 1' == name
73-
74-
date_modified = rows[2].findChildren('td')[1].get_text().strip()
75-
assert '2017-03-02 16:22:14' == date_modified
76-
77-
size = rows[2].findChildren('td')[2].get_text().strip()
78-
assert '15B' == size
7972

80-
# non-expanded zip file should have no children
81-
name = rows[4].findChildren('td')[0].get_text().strip()
82-
assert 'test/zip file which is not expanded.zip' == name
83-
assert body.count('zip file which is not expanded.zip') == 1
73+
def test_filelist_to_tree(self, renderer, zip_file_tree, file_tree):
8474

85-
size = rows[4].findChildren('td')[2].get_text().strip()
86-
assert '1.8KB' == size # formatting of larger byte sizes
75+
files = [file for file in zip_file_tree.filelist if not file.filename.startswith('__MACOSX')]
8776

88-
# hidden files should be hidden
89-
assert body.count('__MACOSX') == 0
77+
actual = renderer.filelist_to_tree(files)
78+
assert actual == file_tree
9079

0 commit comments

Comments
 (0)