Skip to content

Commit fdc9754

Browse files
wil93stefano-maggiolo
authored andcommitted
CWS: use cmscommon.archive when suitable
1 parent 82d690c commit fdc9754

3 files changed

Lines changed: 73 additions & 74 deletions

File tree

cms/server/ContestWebServer.py

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
# Copyright © 2013 Bernard Blackham <bernard@largestprime.net>
1010
# Copyright © 2014 Artem Iglikov <artem.iglikov@gmail.com>
1111
# Copyright © 2014 Fabian Gundlach <320pointsguy@gmail.com>
12+
# Copyright © 2015 William Di Luigi <williamdiluigi@gmail.com>
1213
#
1314
# This program is free software: you can redistribute it and/or modify
1415
# it under the terms of the GNU Affero General Public License as
@@ -53,7 +54,6 @@
5354
import re
5455
import socket
5556
import struct
56-
import tempfile
5757
import traceback
5858
from datetime import timedelta
5959
from urllib import quote
@@ -72,15 +72,16 @@
7272
from cms.db.filecacher import FileCacher
7373
from cms.grading.tasktypes import get_task_type
7474
from cms.grading.scoretypes import get_score_type
75-
from cms.server import file_handler_gen, extract_archive, \
76-
actual_phase_required, get_url_root, filter_ascii, \
77-
CommonRequestHandler, format_size, compute_actual_phase
75+
from cms.server import file_handler_gen, actual_phase_required, \
76+
get_url_root, filter_ascii, CommonRequestHandler, format_size, \
77+
compute_actual_phase
7878
from cmscommon.isocodes import is_language_code, translate_language_code, \
7979
is_country_code, translate_country_code, \
8080
is_language_country_code, translate_language_country_code
8181
from cmscommon.crypto import encrypt_number
8282
from cmscommon.datetime import make_datetime, make_timestamp, get_timezone
8383
from cmscommon.mimetypes import get_type_for_file_name
84+
from cmscommon.archive import Archive
8485

8586

8687
logger = logging.getLogger(__name__)
@@ -979,16 +980,10 @@ def post(self, task_name):
979980
archive_data = self.request.files["submission"][0]
980981
del self.request.files["submission"]
981982

982-
# Extract the files from the archive.
983-
temp_archive_file, temp_archive_filename = \
984-
tempfile.mkstemp(dir=config.temp_dir)
985-
with os.fdopen(temp_archive_file, "w") as temp_archive_file:
986-
temp_archive_file.write(archive_data["body"])
983+
# Create the archive.
984+
archive = Archive.from_raw_data(archive_data["body"])
987985

988-
archive_contents = extract_archive(temp_archive_filename,
989-
archive_data["filename"])
990-
991-
if archive_contents is None:
986+
if archive is None:
992987
self.application.service.add_notification(
993988
self.current_user.username,
994989
self.timestamp,
@@ -999,8 +994,17 @@ def post(self, task_name):
999994
safe=''))
1000995
return
1001996

1002-
for item in archive_contents:
1003-
self.request.files[item["filename"]] = [item]
997+
# Extract the archive.
998+
unpacked_dir = archive.unpack()
999+
for name in archive.namelist():
1000+
filename = os.path.basename(name)
1001+
body = open(os.path.join(unpacked_dir, filename), "r").read()
1002+
self.request.files[filename] = [{
1003+
'filename': filename,
1004+
'body': body
1005+
}]
1006+
1007+
archive.cleanup()
10041008

10051009
# This ensure that the user sent one file for every name in
10061010
# submission format and no more. Less is acceptable if task
@@ -1518,16 +1522,10 @@ def post(self, task_name):
15181522
archive_data = self.request.files["submission"][0]
15191523
del self.request.files["submission"]
15201524

1521-
# Extract the files from the archive.
1522-
temp_archive_file, temp_archive_filename = \
1523-
tempfile.mkstemp(dir=config.temp_dir)
1524-
with os.fdopen(temp_archive_file, "w") as temp_archive_file:
1525-
temp_archive_file.write(archive_data["body"])
1526-
1527-
archive_contents = extract_archive(temp_archive_filename,
1528-
archive_data["filename"])
1525+
# Create the archive.
1526+
archive = Archive.from_raw_data(archive_data["body"])
15291527

1530-
if archive_contents is None:
1528+
if archive is None:
15311529
self.application.service.add_notification(
15321530
self.current_user.username,
15331531
self.timestamp,
@@ -1537,8 +1535,17 @@ def post(self, task_name):
15371535
self.redirect("/testing?%s" % quote(task.name, safe=''))
15381536
return
15391537

1540-
for item in archive_contents:
1541-
self.request.files[item["filename"]] = [item]
1538+
# Extract the archive.
1539+
unpacked_dir = archive.unpack()
1540+
for name in archive.namelist():
1541+
filename = os.path.basename(name)
1542+
body = open(os.path.join(unpacked_dir, filename), "r").read()
1543+
self.request.files[filename] = [{
1544+
'filename': filename,
1545+
'body': body
1546+
}]
1547+
1548+
archive.cleanup()
15421549

15431550
# This ensure that the user sent one file for every name in
15441551
# submission format and no more. Less is acceptable if task

cms/server/__init__.py

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@
3030

3131
import time
3232
import logging
33-
import tarfile
34-
import zipfile
3533
from datetime import datetime, timedelta
3634
from urllib import quote
3735

@@ -199,49 +197,6 @@ def wrapped(self, *args, **kwargs):
199197
return decorator
200198

201199

202-
def extract_archive(temp_name, original_filename):
203-
"""Obtain a list of files inside the specified archive.
204-
205-
Returns a list of the files inside the archive located in
206-
temp_name, using original_filename to guess the type of the
207-
archive.
208-
209-
"""
210-
file_list = []
211-
if original_filename.endswith(".zip"):
212-
try:
213-
zip_object = zipfile.ZipFile(temp_name, "r")
214-
for item in zip_object.infolist():
215-
file_list.append({
216-
"filename": item.filename,
217-
"body": zip_object.read(item)})
218-
except Exception as error:
219-
logger.warning("Exception while extracting zip file `%s'. %r",
220-
original_filename, error)
221-
return None
222-
elif original_filename.endswith(".tar.gz") \
223-
or original_filename.endswith(".tar.bz2") \
224-
or original_filename.endswith(".tar"):
225-
try:
226-
tar_object = tarfile.open(name=temp_name)
227-
for item in tar_object.getmembers():
228-
if item.isfile():
229-
file_list.append({
230-
"filename": item.name,
231-
"body": tar_object.extractfile(item).read()})
232-
except tarfile.TarError:
233-
logger.warning("Exception while extracting tar file `%s'. %r",
234-
original_filename, error)
235-
return None
236-
except IOError:
237-
return None
238-
else:
239-
logger.warning("Compressed file `%s' not recognized.",
240-
original_filename)
241-
return None
242-
return file_list
243-
244-
245200
UNITS = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
246201
DIMS = list(1024 ** x for x in xrange(9))
247202

cmscommon/archive.py

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# -*- coding: utf-8 -*-
33

44
# Contest Management System - http://cms-dev.github.io/
5-
# Copyright © 2014 William Di Luigi <williamdiluigi@gmail.com>
5+
# Copyright © 2014-2015 William Di Luigi <williamdiluigi@gmail.com>
66
# Copyright © 2014 Stefano Maggiolo <s.maggiolo@gmail.com>
77
#
88
# This program is free software: you can redistribute it and/or modify
@@ -33,6 +33,8 @@
3333

3434
from patoolib.util import PatoolError
3535

36+
from cms import config
37+
3638

3739
class ArchiveException(Exception):
3840
"""Exception for when the interaction with the Archive class is
@@ -92,14 +94,43 @@ def extract_to_dir(archive_path, to_dir):
9294
"""
9395
patoolib.extract_archive(archive_path, outdir=to_dir)
9496

95-
def __init__(self, path):
97+
@staticmethod
98+
def from_raw_data(raw_data):
99+
"""Create an Archive object out of raw archive data.
100+
101+
This method treats the given string as archive data: it dumps it
102+
into a temporary file, then creates an Archive object. Since the
103+
user did not provide a path, we assume that when cleanup() is
104+
called the temporary file should be deleted as well as unpacked
105+
data.
106+
107+
raw_data (bytes): the actual bytes that form the archive.
108+
109+
return (Archive|None): an object that represents the new
110+
archive or None, if raw_data doesn't represent an archive.
111+
112+
"""
113+
temp_file, temp_filename = tempfile.mkstemp(dir=config.temp_dir)
114+
with os.fdopen(temp_file, "w") as temp_file:
115+
temp_file.write(raw_data)
116+
117+
try:
118+
return Archive(temp_filename, delete_source=True)
119+
except ArchiveException:
120+
os.remove(temp_filename)
121+
return None
122+
123+
def __init__(self, path, delete_source=False):
96124
"""Init.
97125
98126
path (string): the path of the archive.
127+
delete_source (bool): whether the source archive should be
128+
deleted at cleanup or not.
99129
100130
"""
101131
if not Archive.is_supported(path):
102132
raise ArchiveException("This type of archive is not supported.")
133+
self.delete_source = delete_source
103134
self.path = path
104135
self.temp_dir = None
105136

@@ -109,7 +140,7 @@ def unpack(self):
109140
return (string): the path of the temporary directory.
110141
111142
"""
112-
self.temp_dir = tempfile.mkdtemp()
143+
self.temp_dir = tempfile.mkdtemp(dir=config.temp_dir)
113144
patoolib.extract_archive(self.path, outdir=self.temp_dir)
114145
return self.temp_dir
115146

@@ -131,6 +162,12 @@ def cleanup(self):
131162
if os.path.exists(self.temp_dir):
132163
shutil.rmtree(self.temp_dir)
133164
self.temp_dir = None
165+
if self.delete_source:
166+
try:
167+
os.remove(self.path)
168+
except OSError:
169+
# Cannot delete source, it is not a big problem.
170+
pass
134171

135172
def namelist(self):
136173
"""Returns all pathnames for this archive.

0 commit comments

Comments
 (0)