0 | 0 |
import os
|
1 | 1 |
import stat
|
2 | 2 |
|
3 | |
import tarfile
|
4 | |
from io import BytesIO
|
5 | |
from time import time
|
6 | |
|
7 | 3 |
from flask import request, render_template, current_app
|
8 | 4 |
from flask.views import View
|
9 | 5 |
|
|
12 | 8 |
|
13 | 9 |
from dulwich.objects import Blob
|
14 | 10 |
|
15 | |
from klaus import markup
|
|
11 |
from klaus import markup, tarutils
|
16 | 12 |
from klaus.utils import parent_directory, subpaths, pygmentize, \
|
17 | 13 |
force_unicode, guess_is_binary, guess_is_image
|
18 | 14 |
|
|
242 | 238 |
"""
|
243 | 239 |
def get_response(self):
|
244 | 240 |
tarname = "%s@%s.tar" % (self.context['repo'].name, self.context['rev'])
|
245 | |
resp = Response(self._generator(), mimetype="application/tar")
|
246 | |
resp.headers['Content-Disposition'] = 'attachment; filename=%s' % tarname
|
247 | |
resp.headers['Content-Length'] = str(self._size())
|
248 | |
resp.headers['Cache-Control'] = "no-store" # Disables browser caching
|
249 | |
return resp
|
250 | |
|
251 | |
@staticmethod
|
252 | |
def _io_len(s):
|
253 | |
pos = s.tell()
|
254 | |
s.seek(0, os.SEEK_END)
|
255 | |
length = s.tell()
|
256 | |
s.seek(pos)
|
257 | |
return length
|
258 | |
|
259 | |
def _walker(self, directory=''):
|
260 | |
root_tree = self.context['repo'].get_blob_or_tree(
|
261 | |
self.context['commit'], directory
|
262 | |
)
|
263 | |
for entry in root_tree.iteritems():
|
264 | |
name, entry = entry.path, entry.in_path(directory)
|
265 | |
if entry.mode & stat.S_IFDIR:
|
266 | |
for f in self._walker(entry.path):
|
267 | |
yield f
|
268 | |
else:
|
269 | |
data = self.context['repo'].get_blob_or_tree(
|
270 | |
self.context['commit'], entry.path
|
271 | |
).as_raw_string()
|
272 | |
yield (entry.path, BytesIO(data), entry.mode)
|
273 | |
|
274 | |
def _size(self, directory=''):
|
275 | |
root_tree = self.context['repo'].get_blob_or_tree(
|
276 | |
self.context['commit'], directory
|
277 | |
)
|
278 | |
size = 0
|
279 | |
for entry in root_tree.iteritems():
|
280 | |
name, entry = entry.path, entry.in_path(directory)
|
281 | |
if entry.mode & stat.S_IFDIR:
|
282 | |
size += self._size(entry.path)
|
283 | |
else:
|
284 | |
size += self.context['repo'].get_blob_or_tree(
|
285 | |
self.context['commit'], entry.path
|
286 | |
).raw_length()
|
287 | |
#see https://en.wikipedia.org/wiki/Tar_%28file_format%29#File_header
|
288 | |
size += 512 + 512 # info + padding
|
289 | |
return size + 512
|
290 | |
|
291 | |
def _generator(self):
|
292 | |
buf = BytesIO()
|
293 | |
tar = tarfile.open("repo.tar", "w", buf)
|
294 | |
for fl, f, mode in self._walker():
|
295 | |
info = tarfile.TarInfo(name=fl)
|
296 | |
info.size = self._io_len(f)
|
297 | |
info.mode = mode
|
298 | |
info.mtime = self.context['commit'].commit_time
|
299 | |
tar.addfile(info, f)
|
300 | |
yield buf.getvalue()
|
301 | |
buf.truncate(0)
|
302 | |
buf.seek(0)
|
303 | |
tar.close()
|
304 | |
yield buf.getvalue()
|
305 | |
|
306 | |
|
307 | |
# TODO v
|
|
241 |
headers = {
|
|
242 |
'Content-Disposition': "attachment; filename=%s" % tarname,
|
|
243 |
'Cache-Control': "no-store", # Disables browser caching
|
|
244 |
}
|
|
245 |
|
|
246 |
tar_stream = tarutils.tar_stream(
|
|
247 |
self.context['repo'],
|
|
248 |
self.context['blob_or_tree'],
|
|
249 |
self.context['commit'].commit_time
|
|
250 |
)
|
|
251 |
return Response(
|
|
252 |
tar_stream,
|
|
253 |
mimetype="application/tar",
|
|
254 |
headers=headers
|
|
255 |
)
|
|
256 |
|
|
257 |
|
308 | 258 |
history = HistoryView.as_view('history', 'history', 'history.html')
|
309 | 259 |
commit = BaseRepoView.as_view('commit', 'commit', 'view_commit.html')
|
310 | 260 |
blob = BlobView.as_view('blob', 'blob', 'view_blob.html')
|