--- /dev/null
+
+#
+# Copyright 2016 RIFT.IO Inc
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import os
+
+import tornado
+import tornado.web
+import tornado.gen
+
+
+class File:
+ """Convenience class that represents the file
+ """
+ def __init__(self, root_dir, path):
+ self.path = path
+ self.root_dir = root_dir
+ self._meta = None
+
+ @property
+ def relative_path(self):
+ return os.path.relpath(self.path, start=self.root_dir)
+
+ @property
+ def meta(self):
+ """Fetch the meta data for the file.
+ """
+ if not self._meta:
+ self._meta = os.stat(self.path)
+ return self._meta
+
+ def serialize(self):
+ """Converts the object to dict that can be exposed via rest.
+ """
+ data = {}
+ data['name'] = self.relative_path
+ data['last_modified_time'] = self.meta.st_mtime
+ data['byte_size'] = self.meta.st_size
+ return data
+
+class Folder(File):
+ """
+ Convenience class that represents the folder.
+ """
+ def __init__(self, root_dir, path):
+ super().__init__(root_dir, path)
+ self.contents = []
+
+ def serialize(self):
+ """Converts the object to dict that can be exposed via rest.
+ """
+ data = super().serialize()
+ data['contents'] = []
+ for node in self.contents:
+ data['contents'].append(node.serialize())
+ return data
+
+
+class FileRestApiHandler(tornado.web.StaticFileHandler):
+ """Requesthandler class that extends StaticFileHandler. Difference being
+ GETS are now handled at folder level as well and for files we default to
+ the StaticFileHandler
+
+ for the following directory structure
+ Foo
+ |
+ --> bar.py
+
+ <URL>/Foo
+ will generate the list of all files in the directory!
+
+ <URL>/Foo./bar.py
+ will download the file.
+
+ """
+
+ def validate_absolute_path(self, root, absolute_path):
+ """Override the method to disable path validation for directory.
+ """
+ root = os.path.abspath(root)
+ if not root.endswith(os.path.sep):
+ root += os.path.sep
+
+ if not (absolute_path + os.path.sep).startswith(root):
+ raise tornado.web.HTTPError(403, "%s is not in root static directory",
+ self.path)
+ if (os.path.isdir(absolute_path) and
+ self.default_filename is not None):
+ if not self.request.path.endswith("/"):
+ self.redirect(self.request.path + "/", permanent=True)
+ return
+
+ absolute_path = os.path.join(absolute_path, self.default_filename)
+ if not os.path.exists(absolute_path):
+ raise tornado.web.HTTPError(404)
+
+ return absolute_path
+
+ @classmethod
+ def _get_cached_version(cls, abs_path):
+ """Overridden method to disable caching for folder.
+ """
+ if os.path.isdir(abs_path):
+ return None
+
+ return super()._get_cached_version(abs_path)
+
+ @tornado.gen.coroutine
+ def get(self, path, include_body=True):
+ """Override the get method to support both file and folder handling
+ File handling will be handled by StaticFileHandler
+ Folder handling will be done by the derived class.
+ """
+ self.path = self.parse_url_path(path)
+ del path # make sure we don't refer to path instead of self.path again
+ absolute_path = self.get_absolute_path(self.root, self.path)
+
+ self.absolute_path = self.validate_absolute_path(
+ self.root, absolute_path)
+
+ if self.absolute_path is None:
+ return
+
+ if os.path.isfile(absolute_path):
+ super().get(absolute_path)
+ return
+
+ # More meaningful!
+ root_dir = absolute_path
+
+ if not os.path.exists(root_dir):
+ raise tornado.web.HTTPError(404, "File/Folder not found")
+
+ folder_cache = {}
+ for root, dirs, files in os.walk(root_dir):
+ folder = folder_cache.setdefault(
+ root,
+ Folder(root_dir, root))
+
+ # Files
+ for file in files:
+ file_path = os.path.join(root, file)
+ folder.contents.append(
+ File(root_dir, file_path))
+
+ # Sub folders
+ for dir_name in dirs:
+ dir_path = os.path.join(root, dir_name)
+ sub_folder = folder_cache.setdefault(
+ dir_path,
+ Folder(root_dir, dir_path))
+
+ folder.contents.append(sub_folder)
+
+ # Return the root object!
+ structure = folder_cache[root_dir].serialize()
+ self.write(tornado.escape.json_encode(structure))