Skip to content

Fix #2276 #2294

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

Merged
merged 7 commits into from
Sep 19, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions qiita_db/test/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,16 @@ def test_get_timeseries_types(self):
[10, 'mixed', 'combo intervention']]
self.assertEqual(obs, exp)

def test_get_filepath_information(self):
obs = qdb.util.get_filepath_information(1)
# This path is machine specific. Just checking that is not empty
self.assertIsNotNone(obs.pop('fullpath'))
exp = {'filepath_id': 1L, 'filepath': '1_s_G1_L001_sequences.fastq.gz',
'filepath_type': 'raw_forward_seqs', 'checksum': '852952723',
'data_type': 'raw_data', 'mountpoint': 'raw_data',
'subdirectory': False, 'active': True}
self.assertEqual(obs, exp)

def test_filepath_id_to_rel_path(self):
obs = qdb.util.filepath_id_to_rel_path(1)
exp = 'raw_data/1_s_G1_L001_sequences.fastq.gz'
Expand Down
38 changes: 38 additions & 0 deletions qiita_db/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -845,6 +845,44 @@ def move_filepaths_to_upload_folder(study_id, filepaths):
qdb.sql_connection.TRN.execute()


def get_filepath_information(filepath_id):
"""Gets the filepath information of filepath_id

Parameters
----------
filepath_id : int
The filepath id

Returns
-------
dict
The filepath information
"""
with qdb.sql_connection.TRN:
sql = """SELECT filepath_id, filepath, filepath_type, checksum,
data_type, mountpoint, subdirectory, active,
artifact_id
FROM qiita.filepath
JOIN qiita.filepath_type USING (filepath_type_id)
JOIN qiita.data_directory USING (data_directory_id)
LEFT JOIN qiita.artifact_filepath USING (filepath_id)
WHERE filepath_id = %s"""
qdb.sql_connection.TRN.add(sql, [filepath_id])
res = dict(qdb.sql_connection.TRN.execute_fetchindex()[0])

def path_builder(db_dir, filepath, mountpoint, subdirectory, obj_id):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is a nested function necessary? It looks like it only adds complexity here...?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved it outside the function cause it is used in another function, to reduce code duplication.

if subdirectory:
return join(db_dir, mountpoint, str(obj_id), filepath)
else:
return join(db_dir, mountpoint, filepath)

obj_id = res.pop('artifact_id')
res['fullpath'] = path_builder(get_db_files_base_dir(),
res['filepath'], res['mountpoint'],
res['subdirectory'], obj_id)
return res


def filepath_id_to_rel_path(filepath_id):
"""Gets the relative to the base directory of filepath_id

Expand Down
40 changes: 32 additions & 8 deletions qiita_pet/handlers/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
from .base_handlers import BaseHandler
from qiita_pet.handlers.api_proxy import study_get_req
from qiita_db.study import Study
from qiita_db.util import filepath_id_to_rel_path, get_db_files_base_dir
from qiita_db.util import (filepath_id_to_rel_path, get_db_files_base_dir,
get_filepath_information)
from qiita_db.meta_util import validate_filepath_access_by_user
from qiita_db.metadata_template.sample_template import SampleTemplate
from qiita_db.metadata_template.prep_template import PrepTemplate
Expand All @@ -37,19 +38,42 @@ def get(self, filepath_id):
"filepath_id: %s" % (self.current_user.email, str(fid)))

relpath = filepath_id_to_rel_path(fid)
fp_info = get_filepath_information(fid)
fname = basename(relpath)

# If we don't have nginx, write a file that indicates this
self.write("This installation of Qiita was not equipped with nginx, "
"so it is incapable of serving files. The file you "
"attempted to download is located at %s" % relpath)
if fp_info['filepath_type'] in ('directory', 'html_summary_dir'):
# This is a directory, we need to list all the files so NGINX
# can download all of them
basedir = get_db_files_base_dir()
basedir_len = len(basedir) + 1
to_download = []
for dp, _, fps in walk(fp_info['fullpath']):
for fn in fps:
fullpath = join(dp, fn)
spath = fullpath
if fullpath.startswith(basedir):
spath = fullpath[basedir_len:]
to_download.append((fullpath, spath, spath))

all_files = '\n'.join(
["- %s /protected/%s %s" % (getsize(fp), sfp, n)
for fp, sfp, n in to_download])

self.set_header('X-Archive-Files', 'zip')
self.write("%s\n" % all_files)
fname = '%s.zip' % fname
else:
# If we don't have nginx, write a file that indicates this
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of this code is copy pastes from other classes, would you mind creating a new class from which these functions are inherit? Perhaps use the BaseHandlerDownload handler ...

self.write("This installation of Qiita was not equipped with "
"nginx, so it is incapable of serving files. The file "
"you attempted to download is located at %s" % relpath)
self.set_header('Content-Type', 'application/octet-stream')
self.set_header('Content-Transfer-Encoding', 'binary')
self.set_header('X-Accel-Redirect', '/protected/' + relpath)

self.set_header('Content-Description', 'File Transfer')
self.set_header('Content-Type', 'application/octet-stream')
self.set_header('Content-Transfer-Encoding', 'binary')
self.set_header('Expires', '0')
self.set_header('Cache-Control', 'no-cache')
self.set_header('X-Accel-Redirect', '/protected/' + relpath)
self.set_header('Content-Disposition',
'attachment; filename=%s' % fname)

Expand Down
39 changes: 36 additions & 3 deletions qiita_pet/test/test_download.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@

from unittest import main
from mock import Mock
from os.path import exists, isdir, join
from os import remove, makedirs
from os.path import exists, isdir, join, basename
from os import remove, makedirs, close
from shutil import rmtree
from tempfile import mkdtemp
from tempfile import mkdtemp, mkstemp

from biom.util import biom_open
from biom import example_table as et
Expand All @@ -28,9 +28,16 @@ class TestDownloadHandler(TestHandlerBase):

def setUp(self):
super(TestDownloadHandler, self).setUp()
self._clean_up_files = []

def tearDown(self):
super(TestDownloadHandler, self).tearDown()
for fp in self._clean_up_files:
if exists(fp):
if isdir(fp):
rmtree(fp)
else:
remove(fp)

def test_download(self):
# check success
Expand All @@ -45,6 +52,32 @@ def test_download(self):
response = self.get('/download/1000')
self.assertEqual(response.code, 403)

# directory
a = Artifact(1)
fd, fp = mkstemp(suffix='.html')
close(fd)
with open(fp, 'w') as f:
f.write('\n')
self._clean_up_files.append(fp)
dirpath = mkdtemp()
fd, fp2 = mkstemp(suffix='.txt', dir=dirpath)
close(fd)
with open(fp2, 'w') as f:
f.write('\n')
self._clean_up_files.append(dirpath)
a.set_html_summary(fp, support_dir=dirpath)
for fp_id, _, fp_type in a.filepaths:
if fp_type == 'html_summary_dir':
break
response = self.get('/download/%d' % fp_id)
self.assertEqual(response.code, 200)

fp_name = basename(fp2)
dirname = basename(dirpath)
self.assertEqual(
response.body, "- 1 /protected/FASTQ/1/%s/%s FASTQ/1/%s/%s\n"
% (dirname, fp_name, dirname, fp_name))


class TestDownloadStudyBIOMSHandler(TestHandlerBase):

Expand Down