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

« back to all changes in this revision

Viewing changes to bin/ivle-fetchsubmissions

Merge from submissions-admin.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
# IVLE - Informatics Virtual Learning Environment
 
3
# Copyright (C) 2007-2009 The University of Melbourne
 
4
#
 
5
# This program is free software; you can redistribute it and/or modify
 
6
# it under the terms of the GNU General Public License as published by
 
7
# the Free Software Foundation; either version 2 of the License, or
 
8
# (at your option) any later version.
 
9
#
 
10
# This program is distributed in the hope that it will be useful,
 
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
13
# GNU General Public License for more details.
 
14
#
 
15
# You should have received a copy of the GNU General Public License
 
16
# along with this program; if not, write to the Free Software
 
17
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
18
 
 
19
# Program: Fetch Submissions
 
20
# Author:  Matt Giuca
 
21
 
 
22
# Script to retrieve all submissions for a particular project.
 
23
# Requires root to run.
 
24
 
 
25
import sys
 
26
import os
 
27
import shutil
 
28
import datetime
 
29
import optparse
 
30
import zipfile
 
31
import traceback
 
32
 
 
33
import pysvn
 
34
 
 
35
if os.getuid() != 0:
 
36
    print >>sys.stderr, "Must run %s as root." % os.path.basename(sys.argv[0])
 
37
    sys.exit()
 
38
 
 
39
import ivle.config
 
40
import ivle.database
 
41
 
 
42
from ivle.database import Project, ProjectSet, Offering, Subject
 
43
 
 
44
def fetch_submission(submission, target, svnclient, config, zip=False):
 
45
    """Fetch a submission from a user's repository, and dump it in a given
 
46
    directory.
 
47
    @param submission: Submission object, detailing the submission.
 
48
    @param target: Target directory for the project (will create a
 
49
        subdirectory for each submission).
 
50
    @param svnclient: pysvn.Client object.
 
51
    @param config: Config object.
 
52
    @param zip: If True, zips up the submission.
 
53
    """
 
54
    # submission_name is the name of the user or group who owns the repo
 
55
    submission_name = submission.assessed.principal.name
 
56
    # 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)
 
60
    url = get_repo_url(submission, config)
 
61
    revision = pysvn.Revision(pysvn.opt_revision_kind.number,
 
62
                              submission.revision)
 
63
    svnclient.export(url, target_final, force=True,
 
64
        revision=revision, recurse=True)
 
65
 
 
66
    # If required, zip up the directory
 
67
    if zip:
 
68
        make_zip(target_final, target_final + ".zip")
 
69
        # Remove the target tree
 
70
        shutil.rmtree(target_final)
 
71
 
 
72
def get_repo_url(submission, config):
 
73
    """Gets a local (file:) URL to the repository for a given submission.
 
74
    This will consult submission.path to find the path within the repository
 
75
    to check out.
 
76
    @param submission: Submission object, detailing the submission.
 
77
    @param config: Config object.
 
78
    """
 
79
    # NOTE: This code is mostly copied from services/usrmgt-server
 
80
    if submission.assessed.is_group:
 
81
        # The offering this group is in
 
82
        offering = submission.assessed.project.project_set.offering
 
83
        groupname = submission.assessed.principal.name
 
84
        # The name of the repository directory within 'groups' is
 
85
        # SUBJECT_YEAR_SEMESTER_GROUP
 
86
        namespace = "_".join([offering.subject.short_name,
 
87
            offering.semester.year, offering.semester.semester, groupname])
 
88
        repo_path = os.path.join(config['paths']['svn']['repo_path'],
 
89
                                'groups', namespace)
 
90
    else:
 
91
        # The name of the repository directory within 'users' is the username
 
92
        username = submission.assessed.principal.name
 
93
        repo_path = os.path.join(config['paths']['svn']['repo_path'],
 
94
                                'users', username)
 
95
 
 
96
    path_in_repo = submission.path
 
97
    # Change an absolute path into a relative one (to the top of SVN)
 
98
    if path_in_repo[:1] == os.sep or path_in_repo[:1] == os.altsep:
 
99
        path_in_repo = path_in_repo[1:]
 
100
 
 
101
    # Attach "file://" to the front of the absolute path, to make it a URL
 
102
    return "file://" + os.path.join(os.path.abspath(repo_path), path_in_repo)
 
103
 
 
104
def make_zip(source, dest):
 
105
    """Zip up a directory tree or file. The ZIP file will always contain just
 
106
    a single directory or file at the top level (it will not be a ZIP bomb).
 
107
    @param source: Path to a directory or file to zip up.
 
108
    @param dest: Path to a zip file to create.
 
109
    """
 
110
    # NOTE: This code is mostly copied from ivle.zip (but very different)
 
111
    zip = zipfile.ZipFile(dest, 'w')
 
112
 
 
113
    # Write the source file/directory itself
 
114
    # (If this is a directory it will NOT recurse)
 
115
    zip.write(source, os.path.basename(source))
 
116
 
 
117
    if os.path.isdir(source):
 
118
        # All paths within the zip file are relative to relativeto
 
119
        relativeto = os.path.dirname(source)
 
120
        # Write the top-level directory
 
121
        # Walk the directory tree
 
122
        def error(err):
 
123
            raise OSError("Could not access a file (zipping)")
 
124
        for (dirpath, dirnames, filenames) in \
 
125
            os.walk(source, onerror=error):
 
126
            arc_dirpath = os.path.relpath(dirpath, relativeto)
 
127
            for filename in dirnames + filenames:
 
128
                zip.write(os.path.join(dirpath, filename),
 
129
                            os.path.join(arc_dirpath, filename))
 
130
 
 
131
    zip.close()
 
132
 
 
133
def main(argv=None):
 
134
    global store
 
135
    if argv is None:
 
136
        argv = sys.argv
 
137
 
 
138
    usage = """usage: %prog [options] subject projname
 
139
    (requires root)
 
140
    Retrieves all submissions for a given project. Places each submission in
 
141
    its own directory, in a subdirectory of '.'. Any errors are reported to
 
142
    stderr (otherwise is silent).
 
143
    subject/projname is the subject/project's short name.
 
144
    """
 
145
 
 
146
    # Parse arguments
 
147
    parser = optparse.OptionParser(usage)
 
148
    parser.add_option("-s", "--semester",
 
149
        action="store", dest="semester", metavar="YEAR/SEMESTER",
 
150
        help="Semester of the subject's offering (eg. 2009/1). "
 
151
             "Defaults to the currently active semester.",
 
152
        default=None)
 
153
    parser.add_option("-d", "--dest",
 
154
        action="store", dest="dest", metavar="PATH",
 
155
        help="Destination directory (default to '.', creates a subdirectory, "
 
156
            "so will not pollute PATH).",
 
157
        default=".")
 
158
    parser.add_option("-z", "--zip",
 
159
        action="store_true", dest="zip",
 
160
        help="Store each submission in a Zip file.",
 
161
        default=False)
 
162
    (options, args) = parser.parse_args(argv[1:])
 
163
 
 
164
    if len(args) < 2:
 
165
        parser.print_help()
 
166
        parser.exit()
 
167
 
 
168
    subject_name = unicode(args[0])
 
169
    project_name = unicode(args[1])
 
170
 
 
171
    if options.semester is None:
 
172
        year, semester = None, None
 
173
    else:
 
174
        try:
 
175
            year, semester = options.semester.split('/')
 
176
            if len(year) == 0 or len(semester) == 0:
 
177
                raise ValueError()
 
178
        except ValueError:
 
179
            parser.error('Invalid semester (must have form "year/semester")')
 
180
 
 
181
    svnclient = pysvn.Client()
 
182
    config = ivle.config.Config(plugins=False)
 
183
    store = ivle.database.get_store(config)
 
184
 
 
185
    # Get the subject from the DB
 
186
    subject = store.find(Subject,
 
187
                     Subject.short_name == subject_name).one()
 
188
    if subject is None:
 
189
        print >>sys.stderr, "No subject with short name '%s'" % subject_name
 
190
        return 1
 
191
 
 
192
    # Get the offering from the DB
 
193
    if semester is None:
 
194
        # None specified - get the current offering from the DB
 
195
        offerings = list(subject.active_offerings())
 
196
        if len(offerings) == 0:
 
197
            print >>sys.stderr, ("No active offering for subject '%s'"
 
198
                                 % subject_name)
 
199
            return 1
 
200
        elif len(offerings) > 1:
 
201
            print >>sys.stderr, ("Multiple active offerings for subject '%s':"
 
202
                                 % subject_name)
 
203
            print >>sys.stderr, "Please use one of:"
 
204
            for offering in offerings:
 
205
                print >>sys.stderr, ("    --semester=%s/%s"
 
206
                    % (offering.semester.year, offering.semester.semester))
 
207
            return 1
 
208
        else:
 
209
            offering = offerings[0]
 
210
    else:
 
211
        # Get the offering for the specified semester
 
212
        offering = subject.offering_for_semester(year, semester)
 
213
        if offering is None:
 
214
            print >>sys.stderr, (
 
215
                "No offering for subject '%s' in semester %s/%s"
 
216
                % (subject_name, year, semester))
 
217
            return 1
 
218
 
 
219
    # Get the project from the DB
 
220
    project = store.find(Project,
 
221
                         Project.project_set_id == ProjectSet.id,
 
222
                         ProjectSet.offering == offering,
 
223
                         Project.short_name == project_name).one()
 
224
    if project is None:
 
225
        print >>sys.stderr, "No project with short name '%s'" % project_name
 
226
        return 1
 
227
 
 
228
    # Target directory is DEST/subject/year/semester/project
 
229
    target_dir = os.path.join(options.dest, subject_name,
 
230
        offering.semester.year, offering.semester.semester, project_name)
 
231
    if not os.path.exists(target_dir):
 
232
        os.makedirs(target_dir)
 
233
 
 
234
    for submission in project.latest_submissions:
 
235
        try:
 
236
            fetch_submission(submission, target_dir, svnclient, config,
 
237
                             zip=options.zip)
 
238
        except Exception, e:
 
239
            # Catch all exceptions (to ensure if one student has a problem, it
 
240
            # is reported, and we can continue)
 
241
            print >>sys.stderr, "ERROR on submission for %s:" % (
 
242
                submission.assessed.principal.display_name)
 
243
            traceback.print_exc(e)
 
244
 
 
245
if __name__ == "__main__":
 
246
    sys.exit(main(sys.argv))