update from RIFT as of 696b75d2fe9fb046261b08c616f1bcf6c0b54a9b second try
[osm/SO.git] / rwlaunchpad / plugins / rwlaunchpadtasklet / rift / package / handler.py
1
2 #
3 # Copyright 2016 RIFT.IO Inc
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16 #
17
18 import os
19
20 import tornado
21 import tornado.web
22 import tornado.gen
23
24
25 class File:
26 """Convenience class that represents the file
27 """
28 def __init__(self, root_dir, path):
29 self.path = path
30 self.root_dir = root_dir
31 self._meta = None
32
33 @property
34 def relative_path(self):
35 return os.path.relpath(self.path, start=self.root_dir)
36
37 @property
38 def meta(self):
39 """Fetch the meta data for the file.
40 """
41 if not self._meta:
42 self._meta = os.stat(self.path)
43 return self._meta
44
45 def serialize(self):
46 """Converts the object to dict that can be exposed via rest.
47 """
48 data = {}
49 data['name'] = self.relative_path
50 data['last_modified_time'] = self.meta.st_mtime
51 data['byte_size'] = self.meta.st_size
52 return data
53
54 class Folder(File):
55 """
56 Convenience class that represents the folder.
57 """
58 def __init__(self, root_dir, path):
59 super().__init__(root_dir, path)
60 self.contents = []
61
62 def serialize(self):
63 """Converts the object to dict that can be exposed via rest.
64 """
65 data = super().serialize()
66 data['contents'] = []
67 for node in self.contents:
68 data['contents'].append(node.serialize())
69 return data
70
71
72 class FileRestApiHandler(tornado.web.StaticFileHandler):
73 """Requesthandler class that extends StaticFileHandler. Difference being
74 GETS are now handled at folder level as well and for files we default to
75 the StaticFileHandler
76
77 for the following directory structure
78 Foo
79 |
80 --> bar.py
81
82 <URL>/Foo
83 will generate the list of all files in the directory!
84
85 <URL>/Foo./bar.py
86 will download the file.
87
88 """
89
90 def validate_absolute_path(self, root, absolute_path):
91 """Override the method to disable path validation for directory.
92 """
93 root = os.path.abspath(root)
94 if not root.endswith(os.path.sep):
95 root += os.path.sep
96
97 if not (absolute_path + os.path.sep).startswith(root):
98 raise tornado.web.HTTPError(403, "%s is not in root static directory",
99 self.path)
100 if (os.path.isdir(absolute_path) and
101 self.default_filename is not None):
102 if not self.request.path.endswith("/"):
103 self.redirect(self.request.path + "/", permanent=True)
104 return
105
106 absolute_path = os.path.join(absolute_path, self.default_filename)
107 if not os.path.exists(absolute_path):
108 raise tornado.web.HTTPError(404)
109
110 return absolute_path
111
112 @classmethod
113 def _get_cached_version(cls, abs_path):
114 """Overridden method to disable caching for folder.
115 """
116 if os.path.isdir(abs_path):
117 return None
118
119 return super()._get_cached_version(abs_path)
120
121 @tornado.gen.coroutine
122 def get(self, path, include_body=True):
123 """Override the get method to support both file and folder handling
124 File handling will be handled by StaticFileHandler
125 Folder handling will be done by the derived class.
126 """
127 self.path = self.parse_url_path(path)
128 del path # make sure we don't refer to path instead of self.path again
129 absolute_path = self.get_absolute_path(self.root, self.path)
130
131 self.absolute_path = self.validate_absolute_path(
132 self.root, absolute_path)
133
134 if self.absolute_path is None:
135 return
136
137 if os.path.isfile(absolute_path):
138 super().get(absolute_path)
139 return
140
141 # More meaningful!
142 root_dir = absolute_path
143
144 if not os.path.exists(root_dir):
145 raise tornado.web.HTTPError(404, "File/Folder not found")
146
147 folder_cache = {}
148 for root, dirs, files in os.walk(root_dir):
149 folder = folder_cache.setdefault(
150 root,
151 Folder(root_dir, root))
152
153 # Files
154 for file in files:
155 file_path = os.path.join(root, file)
156 folder.contents.append(
157 File(root_dir, file_path))
158
159 # Sub folders
160 for dir_name in dirs:
161 dir_path = os.path.join(root, dir_name)
162 sub_folder = folder_cache.setdefault(
163 dir_path,
164 Folder(root_dir, dir_path))
165
166 folder.contents.append(sub_folder)
167
168 # Return the root object!
169 structure = folder_cache[root_dir].serialize()
170 self.set_header('Content-Type','application/json')
171 self.write(tornado.escape.json_encode(structure))