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

« back to all changes in this revision

Viewing changes to bin/ivle-fetchsubmissions

  • Committer: mattgiuca
  • Date: 2008-07-15 07:19:34 UTC
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:trunk:875
Added "migrations" directory, which contains incremental database update
    scripts.
Updated users.sql, uniqueness key on offering table.
Added migration matching this update to the migrations directory. Mm handy!

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))