~azzar1/unity/add-show-desktop-key

« back to all changes in this revision

Viewing changes to bin/ivle-fetchsubmissions

Semesters now have separate URL name, display name and code attributes.

Show diffs side-by-side

added added

removed removed

Lines of Context:
22
22
# Script to retrieve all submissions for a particular project.
23
23
# Requires root to run.
24
24
 
 
25
from __future__ import with_statement
 
26
 
25
27
import sys
26
28
import os
27
29
import shutil
28
30
import datetime
 
31
import codecs
29
32
import optparse
30
33
import zipfile
31
34
import traceback
 
35
import locale
32
36
 
33
37
import pysvn
34
38
 
38
42
 
39
43
import ivle.config
40
44
import ivle.database
 
45
import ivle.util
41
46
 
42
47
from ivle.database import Project, ProjectSet, Offering, Subject
43
48
 
44
 
def fetch_submission(submission, target, svnclient, config, zip=False):
 
49
# Set locale to UTF-8
 
50
locale.setlocale(locale.LC_CTYPE, "en_US.UTF-8")
 
51
 
 
52
# Is Python version 2.6 or higher?
 
53
PYTHON26 = map(int, sys.version[:3].split('.')) >= [2, 6]
 
54
 
 
55
def fetch_submission(submission, target, svnclient, config, zip=False,
 
56
    txt=False, verbose=False):
45
57
    """Fetch a submission from a user's repository, and dump it in a given
46
58
    directory.
47
59
    @param submission: Submission object, detailing the submission.
50
62
    @param svnclient: pysvn.Client object.
51
63
    @param config: Config object.
52
64
    @param zip: If True, zips up the submission.
 
65
    @param txt: If True, writes an extra text file with metadata about the
 
66
        submission.
 
67
    @param verbose: If True, writes the name of each submission to stdout.
53
68
    """
54
69
    # submission_name is the name of the user or group who owns the repo
55
 
    submission_name = submission.assessed.principal.name
 
70
    submission_name = submission.assessed.principal.short_name
56
71
    # target_final is the directory to place the files in
57
 
    target_final = os.path.join(target, submission.assessed.principal.name)
58
 
    if not os.path.exists(target_final):
59
 
        os.makedirs(target_final)
 
72
    target_final = os.path.join(target,
 
73
                                submission.assessed.principal.short_name)
 
74
    target_final = target_final.encode('utf-8')
 
75
    if os.path.exists(target_final):
 
76
        # Remove the existing file or directory before re-checking out
 
77
        if os.path.isdir(target_final):
 
78
            ivle.util.safe_rmtree(target_final)
 
79
        else:
 
80
            os.remove(target_final)
60
81
    url = get_repo_url(submission, config)
61
82
    revision = pysvn.Revision(pysvn.opt_revision_kind.number,
62
83
                              submission.revision)
63
84
    svnclient.export(url, target_final, force=True,
64
85
        revision=revision, recurse=True)
65
86
 
 
87
    if txt:
 
88
        # info_final is the directory to place the metadata info files in
 
89
        info_final = target_final + ".txt"
 
90
        write_submission_info(info_final, submission)
 
91
 
66
92
    # If required, zip up the directory
67
93
    if zip:
68
94
        make_zip(target_final, target_final + ".zip")
69
95
        # Remove the target tree
70
 
        shutil.rmtree(target_final)
 
96
        if os.path.isdir(target_final):
 
97
            ivle.util.safe_rmtree(target_final)
 
98
        else:
 
99
            os.remove(target_final)
 
100
 
 
101
    if verbose:
 
102
        print "Exported submission by: %s (%s)" % (
 
103
            submission.assessed.principal.short_name,
 
104
            submission.assessed.principal.display_name)
71
105
 
72
106
def get_repo_url(submission, config):
73
107
    """Gets a local (file:) URL to the repository for a given submission.
80
114
    if submission.assessed.is_group:
81
115
        # The offering this group is in
82
116
        offering = submission.assessed.project.project_set.offering
83
 
        groupname = submission.assessed.principal.name
 
117
        groupname = submission.assessed.principal.short_name
84
118
        # The name of the repository directory within 'groups' is
85
119
        # SUBJECT_YEAR_SEMESTER_GROUP
86
120
        namespace = "_".join([offering.subject.short_name,
87
 
            offering.semester.year, offering.semester.semester, groupname])
 
121
            offering.semester.year, offering.semester.url_name, groupname])
88
122
        repo_path = os.path.join(config['paths']['svn']['repo_path'],
89
123
                                'groups', namespace)
90
124
    else:
91
125
        # The name of the repository directory within 'users' is the username
92
 
        username = submission.assessed.principal.name
 
126
        username = submission.assessed.principal.short_name
93
127
        repo_path = os.path.join(config['paths']['svn']['repo_path'],
94
128
                                'users', username)
95
129
 
104
138
def make_zip(source, dest):
105
139
    """Zip up a directory tree or file. The ZIP file will always contain just
106
140
    a single directory or file at the top level (it will not be a ZIP bomb).
 
141
    XXX In Python 2.5 and earlier, this does NOT create empty directories
 
142
    (it's not possible with the Python2.5 version of zipfile).
107
143
    @param source: Path to a directory or file to zip up.
108
144
    @param dest: Path to a zip file to create.
109
145
    """
112
148
 
113
149
    # Write the source file/directory itself
114
150
    # (If this is a directory it will NOT recurse)
115
 
    zip.write(source, os.path.basename(source))
 
151
    if PYTHON26 or not os.path.isdir(source):
 
152
        # Python < 2.6 errors if you add a directory
 
153
        # (This means you can't add an empty directory)
 
154
        zip.write(source, os.path.basename(source))
116
155
 
117
156
    if os.path.isdir(source):
118
157
        # All paths within the zip file are relative to relativeto
123
162
            raise OSError("Could not access a file (zipping)")
124
163
        for (dirpath, dirnames, filenames) in \
125
164
            os.walk(source, onerror=error):
126
 
            arc_dirpath = os.path.relpath(dirpath, relativeto)
127
 
            for filename in dirnames + filenames:
 
165
            arc_dirpath = ivle.util.relpath(dirpath, relativeto)
 
166
            # Python < 2.6 errors if you add a directory
 
167
            # (This means you can't add an empty directory)
 
168
            if PYTHON26:
 
169
                filenames = dirnames + filenames
 
170
            for filename in filenames:
128
171
                zip.write(os.path.join(dirpath, filename),
129
172
                            os.path.join(arc_dirpath, filename))
130
173
 
 
174
    if not PYTHON26:
 
175
        # XXX Write this _didModify attribute of zip, to trick it into writing
 
176
        # footer bytes even if there are no files in the archive (otherwise it
 
177
        # writes a 0-byte archive which is invalid).
 
178
        # Note that in Python2.6 we avoid this by always writing the top-level
 
179
        # file or directory, at least.
 
180
        zip._didModify = True
131
181
    zip.close()
132
182
 
 
183
def write_submission_info(filename, submission):
 
184
    """Write human-readable meta-data about a submission to a file.
 
185
    @param filename: Filename to write to.
 
186
    @param submission: Submission object.
 
187
    """
 
188
    with codecs.open(filename, 'w', 'utf-8') as f:
 
189
        if submission.assessed.is_group:
 
190
            # A group project
 
191
            print >>f, "Group: %s (%s)" % (
 
192
                submission.assessed.principal.short_name,
 
193
                submission.assessed.principal.display_name)
 
194
        else:
 
195
            # A solo project
 
196
            # Only show the two fields if they are different (only in rare
 
197
            # circumstances)
 
198
            if submission.assessed.principal != submission.submitter:
 
199
                print >>f, "Author: %s (%s)" % (
 
200
                    submission.assessed.principal.short_name,
 
201
                    submission.assessed.principal.display_name)
 
202
        print >>f, "Submitter: %s (%s)" % (
 
203
            submission.submitter.short_name,
 
204
            submission.submitter.display_name)
 
205
        print >>f, "Date: %s" % (
 
206
            submission.date_submitted.strftime("%Y-%m-%d %H:%M:%S"))
 
207
        print >>f, "SVN Revision: %s" % submission.revision
 
208
        print >>f, "SVN Path: %s" % submission.path
 
209
 
133
210
def main(argv=None):
134
211
    global store
135
212
    if argv is None:
159
236
        action="store_true", dest="zip",
160
237
        help="Store each submission in a Zip file.",
161
238
        default=False)
 
239
    parser.add_option("-v", "--verbose",
 
240
        action="store_true", dest="verbose",
 
241
        help="Print out the name of each submission as it is extracted.",
 
242
        default=False)
 
243
    parser.add_option("--no-txt",
 
244
        action="store_false", dest="txt",
 
245
        help="Disable writing a text file with data about each submission.",
 
246
        default=True)
162
247
    (options, args) = parser.parse_args(argv[1:])
163
248
 
164
249
    if len(args) < 2:
227
312
 
228
313
    # Target directory is DEST/subject/year/semester/project
229
314
    target_dir = os.path.join(options.dest, subject_name,
230
 
        offering.semester.year, offering.semester.semester, project_name)
 
315
        offering.semester.year, offering.semester.url_name, project_name)
231
316
    if not os.path.exists(target_dir):
232
317
        os.makedirs(target_dir)
233
318
 
234
319
    for submission in project.latest_submissions:
235
320
        try:
236
321
            fetch_submission(submission, target_dir, svnclient, config,
237
 
                             zip=options.zip)
 
322
                             zip=options.zip, txt=options.txt,
 
323
                             verbose=options.verbose)
238
324
        except Exception, e:
239
325
            # Catch all exceptions (to ensure if one student has a problem, it
240
326
            # is reported, and we can continue)
241
327
            print >>sys.stderr, "ERROR on submission for %s:" % (
242
328
                submission.assessed.principal.display_name)
243
 
            traceback.print_exc(e)
 
329
            traceback.print_exc()
244
330
 
245
331
if __name__ == "__main__":
246
332
    sys.exit(main(sys.argv))