#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
Class for the cart interface.
Allows API to file interactions.
"""
import os
from threading import Thread
from datetime import datetime
from sys import stderr
from tarfile import TarFile
from json import dumps
from six import PY2
import cherrypy
from cherrypy.lib import static
from .tasks import create_cart
from .utils import Cartutils, parse_size
from .orm import Cart
from .config import get_config
if PY2: # pragma: no cover only works with one version of python
# pylint: disable=invalid-name
bytes_type = str
# pylint: enable=invalid-name
else: # pragma: no cover only will work on one version of python
# pylint: disable=invalid-name
[docs] def bytes_type(unicode_obj):
"""Convert the unicode object into bytes."""
return bytes(unicode_obj, 'UTF-8')
# pylint: enable=invalid-name
[docs]def error_page_default(**kwargs):
"""The default error page should always enforce json."""
cherrypy.response.headers['Content-Type'] = 'application/json'
return dumps({
'status': kwargs['status'],
'message': kwargs['message'],
'traceback': kwargs['traceback'],
'version': kwargs['version']
})
[docs]class CartInterfaceError(Exception):
"""
CartInterfaceError.
Basic exception class for this module.
Will be used to throw exceptions up to the top level of the application.
"""
pass
[docs]class CartRoot(object):
"""
Define the methods that can be used for cart request types.
Doctest for the cart generator class
HPSS Doc Tests
"""
exposed = True
# Cherrypy requires these named methods.
# pylint: disable=invalid-name
[docs] @staticmethod
def GET(uid, **kwargs):
"""Download the tar file created by the cart."""
rtn_name = kwargs.get(
'filename', 'data_' + datetime.now().strftime('%Y_%m_%d_%H_%M_%S') + '.tar')
# get the bundle path if available
cart_utils = Cartutils()
Cart.database_connect()
cart_path = cart_utils.available_cart(uid)
Cart.database_close()
if cart_path is False:
# cart not ready
cherrypy.response.status = '202 Accepted'
return bytes_type('The cart is not ready for download.')
elif cart_path is None:
# cart not found
raise cherrypy.HTTPError(
404, 'The cart does not exist or has already been deleted')
if os.path.isdir(cart_path):
# give back bundle here
stderr.flush()
# want to stream the tar file out
(rpipe, wpipe) = os.pipe()
rfd = os.fdopen(rpipe, 'rb')
wfd = os.fdopen(wpipe, 'wb')
def do_work():
"""The child thread writes the data to the pipe."""
mytar = TarFile.open(fileobj=wfd, mode='w|')
mytar.add(cart_path, arcname=rtn_name.replace('.tar', ''))
mytar.close()
wfd.close()
# open the pipe as a file
wthread = Thread(target=do_work)
wthread.daemon = True
wthread.start()
cherrypy.response.stream = True
cherrypy.response.headers['Content-Type'] = 'application/octet-stream'
cherrypy.response.headers['Content-Disposition'] = 'attachment; filename={}'.format(
rtn_name)
xfer_size = parse_size(get_config().get('cartd', 'transfer_size'))
def read():
"""read some size from rfd."""
buf = rfd.read(xfer_size)
while buf:
yield buf
buf = rfd.read(xfer_size)
wthread.join()
return read()
elif os.path.isfile(cart_path):
return static.serve_file(
cart_path,
'application/octet-stream',
'attachment',
rtn_name
)
raise cherrypy.HTTPError(404, 'Not Found')
# Cherrypy requires these named methods.
# pylint: disable=invalid-name
[docs] @staticmethod
@cherrypy.tools.json_out()
def HEAD(uid):
"""Get the status of a carts tar file."""
cart_utils = Cartutils()
Cart.database_connect()
status, message = cart_utils.cart_status(uid)
Cart.database_close()
cherrypy.response.headers['X-Pacifica-Status'] = status
cherrypy.response.headers['X-Pacifica-Message'] = message
if status == 'error':
if 'No cart with uid' in message:
raise cherrypy.HTTPError(404, 'Not Found')
else:
raise cherrypy.HTTPError(500, 'Internal Server Error')
cherrypy.response.status = 204
return 'No Content'
# Cherrypy requires these named methods.
# pylint: disable=invalid-name
[docs] @staticmethod
@cherrypy.tools.json_out()
@cherrypy.tools.json_in()
def POST(uid):
"""Get all the files locally and bundled."""
data = cherrypy.request.json
file_ids = data['fileids']
bundle = data.get(
'bundle',
get_config().getboolean(
'cartd',
'bundle_task'
)
)
Cart.database_connect()
create_cart(file_ids, uid, bundle)
Cart.database_close()
cherrypy.response.status = '201 Created'
return {'message': 'Cart Processing has begun'}
# Cherrypy requires these named methods.
# pylint: disable=invalid-name
[docs] @staticmethod
@cherrypy.tools.json_out()
def DELETE(uid):
"""Delete a cart that has been created."""
cart_utils = Cartutils()
Cart.database_connect()
message = cart_utils.remove_cart(uid)
Cart.database_close()
if message is False:
raise cherrypy.HTTPError(404, 'Not Found')
return {'message': str(message)}