Source code for devilry.utils.delivery_collection

from django.utils.formats import date_format
from django.http import HttpResponse  
from django.conf import settings

from stream_archives import StreamableZip, StreamableTar

[docs]class ArchiveException(Exception): "Archive exceptions"
[docs]def create_archive_from_assignmentgroups(assignmentgroups, file_name, archive_type): """ Creates an archive of type archive_type, named file_name, containing all the deliveries in each of the assignmentgroups in the list assignmentgroups. """ archive = get_archive_from_archive_type(archive_type) it = iter_archive_assignmentgroups(archive, assignmentgroups) response = HttpResponse(it, mimetype="application/%s" % archive_type) response["Content-Disposition"] = "attachment; filename=%s.%s" % \ (file_name, archive_type) return response
[docs]def create_archive_from_delivery(delivery, archive_type): """ Creates an archive of type archive_type, named assignment.get_path(), containing all files in the delivery. """ archive = get_archive_from_archive_type(archive_type) group = delivery.assignment_group assignment = group.parentnode group_name = _get_assignmentgroup_name(group) it = iter_archive_deliveries(archive, group_name, assignment.get_path(), [delivery]) response = HttpResponse(it, mimetype="application/%s" % archive_type) response["Content-Disposition"] = "attachment; filename=%s.%s" % \ (assignment.get_path(), archive_type) return response
[docs]def iter_archive_deliveries(archive, group_name, directory_prefix, deliveries): """ Adds files one by one from the list of deliveries into the archive. After writing each file to the archive, the new bytes in the archive is yielded. If a file is bigger than DEVILRY_MAX_ARCHIVE_CHUNK_SIZE, only DEVILRY_MAX_ARCHIVE_CHUNK_SIZE bytes are written before it's yielded. The returned object is an iterator. """ include_delivery_explanation = False if len(deliveries) > 1: include_delivery_explanation = True multiple_deliveries_content = "Delivery-ID File count Total size"\ " Delivery time \r\n" for delivery in deliveries: metas = delivery.filemetas.all() delivery_size = 0 for f in metas: delivery_size += f.size filename = "%s/%s/%s" % (directory_prefix, group_name, f.filename) if include_delivery_explanation: filename = "%s/%s/%d/%s" % (directory_prefix, group_name, delivery.number, f.filename) # File size is greater than DEVILRY_MAX_ARCHIVE_CHUNK_SIZE bytes # Write only chunks of size DEVILRY_MAX_ARCHIVE_CHUNK_SIZE to the archive if f.size > settings.DEVILRY_MAX_ARCHIVE_CHUNK_SIZE: if not archive.can_write_chunks(): raise ArchiveException("The size of file %s is greater than "\ "the maximum allowed size. Download "\ "stream aborted.") chunk_size = settings.DEVILRY_MAX_ARCHIVE_CHUNK_SIZE # Open file stream for reading file_to_stream = f.read_open() # Open a filestream in the archive archive.open_filestream(filename, f.size) for i in inclusive_range(chunk_size, f.size, chunk_size): bytes = file_to_stream.read(chunk_size) archive.append_file_chunk(bytes, len(bytes)) # Read the chunk from the archive and yield the data yield archive.read() archive.close_filestream() else: bytes = f.read_open().read(f.size) archive.add_file(filename, bytes) # Read the content from the streamable archive and yield the data yield archive.read() if include_delivery_explanation: multiple_deliveries_content += " %3d %3d %5d"\ "%s\r\n" % \ (delivery.number, len(metas), delivery_size, date_format(delivery.time_of_delivery, "DATETIME_FORMAT")) # Adding file explaining multiple deliveries if include_delivery_explanation: archive.add_file("%s/%s/%s" % (directory_prefix, group_name, "Deliveries.txt"), multiple_deliveries_content.encode("ascii"))
[docs]def iter_archive_assignmentgroups(archive, assignmentgroups): """ Creates an archive, adds files delivered by the assignmentgroups and yields the data. """ name_matches = _get_dictionary_with_name_matches(assignmentgroups) for group in assignmentgroups: group_name = _get_assignmentgroup_name(group) # If multiple groups with the same members exists, # postfix the name with assignmentgroup ID. if name_matches[group_name] > 1: group_name = "%s+%d" % (group_name, group.id) deliveries = group.deliveries.all() for bytes in iter_archive_deliveries(archive, group_name, group.parentnode.get_path(), deliveries): yield bytes archive.close() yield archive.read()
def get_archive_from_archive_type(archive_type): """ Checks that the archive_type is either zip, tar or tar.gz, and return the correct archive class. """ archive = None if archive_type == 'zip': archive = StreamableZip() elif archive_type == 'tar' or archive_type == 'tgz' or archive_type == 'tar.gz': archive = StreamableTar(archive_type) else: raise ArchiveException("archive_type is invalid:%s" % archive_type) return archive
[docs]def verify_groups_not_exceeding_max_file_size(assignmentgroups): """ For each assignmentgroups in groups, calls :meth:`verify_deliveries_not_exceeding_max_file_size`. If the size of a file in a delivery exceeds the settings.DEVILRY_MAX_ARCHIVE_CHUNK_SIZE, an ArchiveException is raised. """ for ag in assignmentgroups: verify_deliveries_not_exceeding_max_file_size(ag.deliveries.all())
[docs]def verify_deliveries_not_exceeding_max_file_size(deliveries): """ Goes through all the files in each deliverery, and if the size of a file exceeds the DEVILRY_MAX_ARCHIVE_CHUNK_SIZE, an ArchiveException is raised. """ max_size = settings.DEVILRY_MAX_ARCHIVE_CHUNK_SIZE for d in deliveries: for f_meta in d.filemetas.all(): if f_meta.size > max_size: raise ArchiveException()
def inclusive_range(start, stop, step=1): """ A range() clone, but this includes the right limit as is if the last step doesn't divide on stop """ l = [] x = start while x <= stop: l.append(x) x += step if x > stop: l.append(stop) return l def _get_assignmentgroup_name(assigmentgroup): """ Returns a string containing the group members of the assignmentgroup separated by '-'. """ cands = assigmentgroup.get_candidates() cands = cands.replace(", ", "-") return cands def _get_dictionary_with_name_matches(assignmentgroups): """ Takes a list of assignmentgroups and returns a dictionary containing the count of groups with similar names sa values in the dictionary. """ matches = {} for assigmentgroup in assignmentgroups: name = _get_assignmentgroup_name(assigmentgroup) if matches.has_key(name): matches[name] = matches[name] + 1 else: matches[name] = 1 return matches