Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SVCS-580] Collapsible Zip Rendering #331

Open
wants to merge 12 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
211 changes: 195 additions & 16 deletions mfr/extensions/zip/render.py
@@ -1,36 +1,215 @@
import os
import zipfile
from typing import List, Union
from zipfile import ZipFile, ZipInfo

import markupsafe
from mako.lookup import TemplateLookup

from mfr.core import extension
from mfr.core.utils import sizeof_fmt
from mfr.core.extension import BaseRenderer


class ZipRenderer(extension.BaseRenderer):
class ZipRenderer(BaseRenderer):

TEMPLATE = TemplateLookup(
directories=[
os.path.join(os.path.dirname(__file__), 'templates')
]).get_template('viewer.mako')

def render(self):
zip_file = zipfile.ZipFile(self.file_path, 'r')

filelist = [{'name': markupsafe.escape(file.filename),
'size': sizeof_fmt(int(file.file_size)),
'date': "%d-%02d-%02d %02d:%02d:%02d" % file.date_time[:6]} for file in zip_file.filelist
if not file.filename.startswith('__MACOSX')]

message = '' if filelist else 'This zip file is empty.'

return self.TEMPLATE.render(zipped_filenames=filelist, message=message)

@property
def file_required(self):
return True

@property
def cache_result(self):
return True

def render(self):

zip_file = ZipFile(self.file_path, 'r')

# ``ZipFile.filelist`` contains both files and folders. Using ``obj`` for better clarity.
sorted_obj_list = self.sanitize_obj_list(zip_file.filelist, sort=True)
obj_tree = self.sorted_obj_list_to_tree(sorted_obj_list)

return self.TEMPLATE.render(data=obj_tree, base=self.assets_url)

def sorted_obj_list_to_tree(self, sorted_obj_list: list) -> List[dict]:
"""Build the object tree from a sorted object list. Each node is a dictionary. Leaf nodes
represent files and empty folders. Non-leaf nodes represent non-emtpy folders. Return a
list of dictionary that contains only one element: the root node. The tree can be accessed
and searched via the ``children`` key, of which the value is a list of child nodes.

:param sorted_obj_list: the sorted object list
:rtype: ``List[dict]``
:return: a list that contains only one element: the root node.
"""
# Build the root node of the tree
tree_root = {
'text': self.metadata.name + self.metadata.ext,
'icon': self.assets_url + '/img/file-ext-zip.png',
'children': []
}
# Iterate through each path and build the tree
for obj in sorted_obj_list:
path_from_root = obj.filename
path_segments = [segment for segment in path_from_root.split('/') if segment]
# Find the parent node of the current object, always start from the root node
parent = tree_root
for index, segment in enumerate(path_segments):
# last segment is the current object, parents must have been found, end loop
if index == len(path_segments) - 1:
break
# for a sorted list, every segment on the path must have a tree node
sibling_list = parent.get('children', [])
parent = self.find_node_among_siblings(segment, sibling_list)
# TODO: do we need this assert?
assert parent
# Create a new node, update details and add it to the sibling list
sibling_list = parent.get('children', [])
is_folder = path_from_root[-1] == '/'
new_node = {
'text': path_segments[-1],
'children': [],
}
self.update_node_with_attributes(new_node, obj, is_folder=is_folder)
sibling_list.append(new_node)
return [tree_root, ]

# TODO: should we remove this function?
def unsorted_obj_list_to_tree(self, obj_list: list) -> List[dict]:
"""Build the object tree from an object list. Each node is a dictionary, where leaf nodes
represent empty folders and files and non-leaf nodes represent non-emtpy folders. Return a
list that contains only one element: the root node.

:param obj_list: the object list
:rtype: ``List[dict]``
:return: a list that contains only one element: the root node.
"""
# Build the root node of the tree
tree_root = {
'text': self.metadata.name + self.metadata.ext,
'icon': self.assets_url + '/img/file-ext-zip.png',
'children': []
}
for obj in obj_list:
# For each object, always start from the root of the tree
parent = tree_root
path_from_root = obj.filename
is_folder = path_from_root[-1] == '/'
path_segments = [segment for segment in path_from_root.split('/') if segment]
last_index = len(path_segments) - 1
# Iterate through the path segments list. Add the segment to tree if not already there
# and update the details with the current object if it is the last one along the path.
for index, segment in enumerate(path_segments):
# Check if the segment has already been added
siblings = parent.get('children', [])
current_node = self.find_node_among_siblings(segment, siblings)
# Found
if current_node:
if index == last_index:
# If it is the last segment, this node must be a folder and represents the
# current object. Update it with the objects' info and break.
assert is_folder
self.update_node_with_attributes(current_node, obj, is_folder=is_folder)
break
# Otherwise, jump to the next segment with the current node as the new parent
parent = current_node
continue
# Not found
new_node = {
'text': segment,
'children': [],
}
if index == last_index:
# If it is the last segment, the node represents the current object. Update the
# it with the objects' info, add it to the siblings and break.
self.update_node_with_attributes(new_node, obj, is_folder=is_folder)
siblings.append(new_node)
break
# Otherwise, append the new node to tree, jump to the next segment with the current
# node as the new parent
siblings.append(new_node)
parent = new_node
continue
return [tree_root, ]

def update_node_with_attributes(self, node: dict, obj: ZipInfo, is_folder: bool=True) -> None:
"""Update details (date, size, icon, etc.) of the node with the given object.

:param node: the node to update
:param obj: the object that the node represents
:param is_folder: the folder flag
"""
date = '%d-%02d-%02d %02d:%02d:%02d' % obj.date_time[:6]
size = sizeof_fmt(int(obj.file_size)) if obj.file_size else ''
if is_folder:
icon_path = self.assets_url + '/img/folder.png'
else:
ext = (os.path.splitext(obj.filename)[1].lstrip('.')).lower()
if self.icon_exists(ext):
icon_path = '{}/img/file-ext-{}.png'.format(self.assets_url, ext)
else:
icon_path = '{}/img/file-ext-generic.png'.format(self.assets_url)
node.update({
'icon': icon_path,
'data': {
'date': date,
'size': size,
},
})

@staticmethod
def icon_exists(ext: str) -> bool:
"""Check if an icon exists for the given file type. The extension string is converted to
lower case.

:param ext: the file extension string
:rtype: ``bool``
:return: ``True`` if found, ``False`` otherwise
"""
return os.path.isfile(os.path.join(
os.path.dirname(__file__),
'static',
'img',
'file-ext-{}.png'.format(ext.lower())
))

@staticmethod
def sanitize_obj_list(obj_list: list, sort: bool=False) -> list:
"""Remove macOS system and temporary files with an option flag to sort the list. Current
implementation only removes '__MACOSX/' and '.DS_Store'.

TODO: If necessary, extend the sanitizer to exclude more file types.

:param obj_list: a list of full paths for each file or folder in the zip
:param sort: the flag for returning a sorted list
:rtype: ``list``
:return: a sanitized list
"""
sanitized_obj_list = []
for obj in obj_list:
obj_path = obj.filename
# Ignore macOS '__MACOSX' folder for zip file
if obj_path.startswith('__MACOSX/'):
continue
# Ignore macOS '.DS_STORE' file
if obj_path == '.DS_Store' or obj_path.endswith('/.DS_Store'):
continue
sanitized_obj_list.append(obj)
if sort:
return sorted(sanitized_obj_list, key=lambda obj: obj.filename)
return sanitized_obj_list

@staticmethod
def find_node_among_siblings(segment: str, siblings: list) -> Union[dict, None]:
"""Find the folder or file node represented by the path segment.

:param segment: the path segment
:param siblings: the list containing all sibling nodes
:rtype: ``Union[dict, None]``
:return: the node dictionary if found or ``None`` otherwise
"""
for sibling in siblings:
if sibling.get('text', '') == segment:
return sibling
return None
Binary file added mfr/extensions/zip/static/img/file-ext-3gp.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mfr/extensions/zip/static/img/file-ext-7z.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mfr/extensions/zip/static/img/file-ext-ace.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mfr/extensions/zip/static/img/file-ext-ai.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mfr/extensions/zip/static/img/file-ext-aif.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mfr/extensions/zip/static/img/file-ext-aiff.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mfr/extensions/zip/static/img/file-ext-amr.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mfr/extensions/zip/static/img/file-ext-asf.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mfr/extensions/zip/static/img/file-ext-asx.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mfr/extensions/zip/static/img/file-ext-bat.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mfr/extensions/zip/static/img/file-ext-bin.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mfr/extensions/zip/static/img/file-ext-bmp.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mfr/extensions/zip/static/img/file-ext-bup.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mfr/extensions/zip/static/img/file-ext-cab.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mfr/extensions/zip/static/img/file-ext-cbr.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mfr/extensions/zip/static/img/file-ext-cda.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mfr/extensions/zip/static/img/file-ext-cdl.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mfr/extensions/zip/static/img/file-ext-cdr.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mfr/extensions/zip/static/img/file-ext-chm.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mfr/extensions/zip/static/img/file-ext-dat.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mfr/extensions/zip/static/img/file-ext-divx.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mfr/extensions/zip/static/img/file-ext-dll.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mfr/extensions/zip/static/img/file-ext-dmg.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mfr/extensions/zip/static/img/file-ext-doc.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mfr/extensions/zip/static/img/file-ext-docx.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mfr/extensions/zip/static/img/file-ext-dss.png
Binary file added mfr/extensions/zip/static/img/file-ext-dvf.png
Binary file added mfr/extensions/zip/static/img/file-ext-dwg.png
Binary file added mfr/extensions/zip/static/img/file-ext-eml.png
Binary file added mfr/extensions/zip/static/img/file-ext-eps.png
Binary file added mfr/extensions/zip/static/img/file-ext-exe.png
Binary file added mfr/extensions/zip/static/img/file-ext-fla.png
Binary file added mfr/extensions/zip/static/img/file-ext-flv.png
Binary file added mfr/extensions/zip/static/img/file-ext-generic.png
Binary file added mfr/extensions/zip/static/img/file-ext-gif.png
Binary file added mfr/extensions/zip/static/img/file-ext-gz.png
Binary file added mfr/extensions/zip/static/img/file-ext-hqx.png
Binary file added mfr/extensions/zip/static/img/file-ext-htm.png
Binary file added mfr/extensions/zip/static/img/file-ext-html.png
Binary file added mfr/extensions/zip/static/img/file-ext-ifo.png
Binary file added mfr/extensions/zip/static/img/file-ext-indd.png
Binary file added mfr/extensions/zip/static/img/file-ext-iso.png
Binary file added mfr/extensions/zip/static/img/file-ext-jar.png
Binary file added mfr/extensions/zip/static/img/file-ext-jpeg.png
Binary file added mfr/extensions/zip/static/img/file-ext-jpg.png
Binary file added mfr/extensions/zip/static/img/file-ext-lnk.png
Binary file added mfr/extensions/zip/static/img/file-ext-log.png
Binary file added mfr/extensions/zip/static/img/file-ext-m4a.png
Binary file added mfr/extensions/zip/static/img/file-ext-m4b.png
Binary file added mfr/extensions/zip/static/img/file-ext-m4p.png
Binary file added mfr/extensions/zip/static/img/file-ext-m4v.png
Binary file added mfr/extensions/zip/static/img/file-ext-mcd.png
Binary file added mfr/extensions/zip/static/img/file-ext-mdb.png
Binary file added mfr/extensions/zip/static/img/file-ext-mid.png
Binary file added mfr/extensions/zip/static/img/file-ext-mov.png
Binary file added mfr/extensions/zip/static/img/file-ext-mp2.png
Binary file added mfr/extensions/zip/static/img/file-ext-mp3.png
Binary file added mfr/extensions/zip/static/img/file-ext-mp4.png
Binary file added mfr/extensions/zip/static/img/file-ext-mpeg.png
Binary file added mfr/extensions/zip/static/img/file-ext-mpg.png
Binary file added mfr/extensions/zip/static/img/file-ext-msi.png
Binary file added mfr/extensions/zip/static/img/file-ext-mswmm.png
Binary file added mfr/extensions/zip/static/img/file-ext-ogg.png
Binary file added mfr/extensions/zip/static/img/file-ext-pdf.png
Binary file added mfr/extensions/zip/static/img/file-ext-png.png
Binary file added mfr/extensions/zip/static/img/file-ext-pps.png
Binary file added mfr/extensions/zip/static/img/file-ext-ps.png
Binary file added mfr/extensions/zip/static/img/file-ext-psd.png
Binary file added mfr/extensions/zip/static/img/file-ext-pst.png
Binary file added mfr/extensions/zip/static/img/file-ext-ptb.png
Binary file added mfr/extensions/zip/static/img/file-ext-pub.png
Binary file added mfr/extensions/zip/static/img/file-ext-py.png
Binary file added mfr/extensions/zip/static/img/file-ext-qbb.png
Binary file added mfr/extensions/zip/static/img/file-ext-qbw.png
Binary file added mfr/extensions/zip/static/img/file-ext-qxd.png
Binary file added mfr/extensions/zip/static/img/file-ext-ram.png
Binary file added mfr/extensions/zip/static/img/file-ext-rar.png
Binary file added mfr/extensions/zip/static/img/file-ext-rm.png
Binary file added mfr/extensions/zip/static/img/file-ext-rmvb.png
Binary file added mfr/extensions/zip/static/img/file-ext-rtf.png
Binary file added mfr/extensions/zip/static/img/file-ext-sea.png
Binary file added mfr/extensions/zip/static/img/file-ext-ses.png
Binary file added mfr/extensions/zip/static/img/file-ext-sit.png
Binary file added mfr/extensions/zip/static/img/file-ext-sitx.png
Binary file added mfr/extensions/zip/static/img/file-ext-ss.png
Binary file added mfr/extensions/zip/static/img/file-ext-swf.png
Binary file added mfr/extensions/zip/static/img/file-ext-tgz.png
Binary file added mfr/extensions/zip/static/img/file-ext-thm.png
Binary file added mfr/extensions/zip/static/img/file-ext-tif.png
Binary file added mfr/extensions/zip/static/img/file-ext-tmp.png
Binary file added mfr/extensions/zip/static/img/file-ext-torrent.png
Binary file added mfr/extensions/zip/static/img/file-ext-ttf.png
Binary file added mfr/extensions/zip/static/img/file-ext-txt.png
Binary file added mfr/extensions/zip/static/img/file-ext-vcd.png
Binary file added mfr/extensions/zip/static/img/file-ext-vob.png
Binary file added mfr/extensions/zip/static/img/file-ext-wav.png
Binary file added mfr/extensions/zip/static/img/file-ext-wma.png
Binary file added mfr/extensions/zip/static/img/file-ext-wmv.png
Binary file added mfr/extensions/zip/static/img/file-ext-wps.png
Binary file added mfr/extensions/zip/static/img/file-ext-xls.png
Binary file added mfr/extensions/zip/static/img/file-ext-xlsx.png
Binary file added mfr/extensions/zip/static/img/file-ext-xpi.png
Binary file added mfr/extensions/zip/static/img/file-ext-zip.png
Binary file added mfr/extensions/zip/static/img/folder.png
4 changes: 4 additions & 0 deletions mfr/extensions/zip/static/js/jstree.min.js

Large diffs are not rendered by default.