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

« back to all changes in this revision

Viewing changes to bin/ivle-fetchsubmissions

Removed debugging prints.

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