Commit 3aaa2868 by Julian Arni

Process uploaded chunks.

   Also adds lockfile for asynchronous status updates.
parent 50934d4d
...@@ -4,6 +4,7 @@ import os ...@@ -4,6 +4,7 @@ import os
import tarfile import tarfile
import shutil import shutil
import cgi import cgi
import re
from functools import partial from functools import partial
from tempfile import mkdtemp from tempfile import mkdtemp
from path import path from path import path
...@@ -38,6 +39,9 @@ from util.json_request import JsonResponse ...@@ -38,6 +39,9 @@ from util.json_request import JsonResponse
__all__ = ['asset_index', 'upload_asset', 'import_course', __all__ = ['asset_index', 'upload_asset', 'import_course',
'generate_export_course', 'export_course'] 'generate_export_course', 'export_course']
MAX_UP_LENGTH = 20000352
CONTENT_RE = re.compile(r"(?P<start>\d{1,11})-(?P<stop>\d{1,11})/(?P<end>\d{1,11})")
def assets_to_json_dict(assets): def assets_to_json_dict(assets):
""" """
...@@ -265,47 +269,89 @@ def remove_asset(request, org, course, name): ...@@ -265,47 +269,89 @@ def remove_asset(request, org, course, name):
@login_required @login_required
def import_course(request, org, course, name): def import_course(request, org, course, name):
""" """
This method will handle a POST request to upload and import a .tar.gz file into a specified course This method will handle a POST request to upload and import a .tar.gz file
into a specified course
""" """
location = get_location_and_verify_access(request, org, course, name) location = get_location_and_verify_access(request, org, course, name)
if request.method in ('POST', 'PUT'): if request.method == 'POST':
filename = request.FILES['course-data'].name
if not filename.endswith('.tar.gz'):
return HttpResponse(json.dumps({'ErrMsg': 'We only support uploading a .tar.gz file.'}))
data_root = path(settings.GITHUB_REPO_ROOT) data_root = path(settings.GITHUB_REPO_ROOT)
course_subdir = "{0}-{1}-{2}".format(org, course, name) course_subdir = "{0}-{1}-{2}".format(org, course, name)
course_dir = data_root / course_subdir course_dir = data_root / course_subdir
if not course_dir.isdir():
os.mkdir(course_dir)
filename = request.FILES['course-data'].name
if not filename.endswith('.tar.gz'):
return HttpResponse(json.dumps({
'ErrMsg': 'We only support uploading a .tar.gz file.'
}))
temp_filepath = course_dir / filename temp_filepath = course_dir / filename
if not course_dir.isdir():
os.mkdir(course_dir)
logging.debug('importing course to {0}'.format(temp_filepath)) logging.debug('importing course to {0}'.format(temp_filepath))
# Get upload chunks byte ranges
matches = CONTENT_RE.search(request.META["HTTP_CONTENT_RANGE"])
content_range = matches.groupdict()
# stream out the uploaded files in chunks to disk # stream out the uploaded files in chunks to disk
if int(content_range['start']) == 0:
temp_file = open(temp_filepath, 'wb+') temp_file = open(temp_filepath, 'wb+')
else:
temp_file = open(temp_filepath, 'ab+')
for chunk in request.FILES['course-data'].chunks(): for chunk in request.FILES['course-data'].chunks():
temp_file.write(chunk) temp_file.write(chunk)
temp_file.close() temp_file.close()
size = os.path.getsize(temp_filepath)
if int(content_range['stop']) != int(content_range['end']) - 1:
# More chunks coming
return JsonResponse({
"files": [{
"name": filename,
"size": size,
"deleteUrl": "",
"deleteType": "",
"url": reverse('import_course', kwargs={
'org': location.org,
'course': location.course,
'name': location.name
}),
"thumbnailUrl": ""
}]
})
else: #This was the last chunk.
# 'Lock' with status info.
lock_filepath = data_root / (filename + ".lock")
with open(lock_filepath, 'w+') as lf:
lf.write("Extracting")
tar_file = tarfile.open(temp_filepath) tar_file = tarfile.open(temp_filepath)
tar_file.extractall(course_dir + '/') tar_file.extractall(course_dir + '/')
with open(lock_filepath, 'w+') as lf:
lf.write("Verifying")
# find the 'course.xml' file # find the 'course.xml' file
dirpath = None dirpath = None
for dirpath, _dirnames, filenames in os.walk(course_dir): for dirpath, _dirnames, filenames in os.walk(course_dir):
for filename in filenames: for fname in filenames:
if filename == 'course.xml': if fname == 'course.xml':
break break
if filename == 'course.xml': if fname == 'course.xml':
break break
if filename != 'course.xml': if fname != 'course.xml':
return HttpResponse(json.dumps({'ErrMsg': 'Could not find the course.xml file in the package.'})) return HttpResponse(json.dumps({
'ErrMsg': 'Could not find the course.xml file in the package.'
}))
logging.debug('found course.xml at {0}'.format(dirpath)) logging.debug('found course.xml at {0}'.format(dirpath))
...@@ -313,21 +359,29 @@ def import_course(request, org, course, name): ...@@ -313,21 +359,29 @@ def import_course(request, org, course, name):
for fname in os.listdir(dirpath): for fname in os.listdir(dirpath):
shutil.move(dirpath / fname, course_dir) shutil.move(dirpath / fname, course_dir)
_module_store, course_items = import_from_xml(modulestore('direct'), settings.GITHUB_REPO_ROOT, _module_store, course_items = import_from_xml(
[course_subdir], load_error_modules=False, modulestore('direct'),
settings.GITHUB_REPO_ROOT,
[course_subdir],
load_error_modules=False,
static_content_store=contentstore(), static_content_store=contentstore(),
target_location_namespace=location, target_location_namespace=location,
draft_store=modulestore()) draft_store=modulestore()
)
# we can blow this away when we're done importing. # we can blow this away when we're done importing.
shutil.rmtree(course_dir) shutil.rmtree(course_dir)
logging.debug('new course at {0}'.format(course_items[0].location)) logging.debug('new course at {0}'.format(course_items[0].location))
create_all_course_groups(request.user, course_items[0].location) with open(lock_filepath, 'w') as lf:
lf.write("Updating course")
create_all_course_groups(request.user, course_items[0].location)
logging.debug('created all course groups at {0}'.format(course_items[0].location)) logging.debug('created all course groups at {0}'.format(course_items[0].location))
os.remove(lock_filepath)
return HttpResponse(json.dumps({'Status': 'OK'})) return HttpResponse(json.dumps({'Status': 'OK'}))
else: else:
course_module = modulestore().get_item(location) course_module = modulestore().get_item(location)
...@@ -346,8 +400,8 @@ def import_course(request, org, course, name): ...@@ -346,8 +400,8 @@ def import_course(request, org, course, name):
@login_required @login_required
def generate_export_course(request, org, course, name): def generate_export_course(request, org, course, name):
""" """
This method will serialize out a course to a .tar.gz file which contains a XML-based representation of This method will serialize out a course to a .tar.gz file which contains a
the course XML-based representation of the course
""" """
location = get_location_and_verify_access(request, org, course, name) location = get_location_and_verify_access(request, org, course, name)
course_module = modulestore().get_instance(location.course_id, location) course_module = modulestore().get_instance(location.course_id, location)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment