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

« back to all changes in this revision

Viewing changes to ivle/fileservice_lib/action.py

  • Committer: Matt Giuca
  • Date: 2010-07-22 02:12:36 UTC
  • mfrom: (1812.1.13 late-submit)
  • Revision ID: matt.giuca@gmail.com-20100722021236-k8kt4cqdtywzpk24
Merge from trunk late-submit.
Students may now submit projects after the deadline, but they are warned that the submission is late.
Lecturers are now given data on which submissions were made late, and how many days.
(LP: #598346)

Show diffs side-by-side

added added

removed removed

Lines of Context:
91
91
# action=svnupdate: Bring a file up to date with the head revision.
92
92
#       path:   The path to the file to be updated. Only one file may be
93
93
#               specified.
94
 
#
95
 
# action=svnpublish: Set the "published" flag on a file to True.
96
 
#       path:   The path to the file to be published. Can be specified
97
 
#               multiple times.
98
 
#
99
 
# action=svnunpublish: Set the "published" flag on a file to False.
100
 
#       path:   The path to the file to be unpublished. Can be specified
101
 
#               multiple times.
 
94
#       revision: The revision number to update to. If not provided this
 
95
#               defaults to HEAD.
102
96
#
103
97
# action=svncommit: Commit a file(s) or directory(s) to the repository.
104
98
#       path:   The path to the file or directory to be committed. Can be
119
113
#       path:   The path to the directory to be checked (under the IVLE
120
114
#               repository base).
121
115
#
 
116
# action=svncleanup: Recursively clean up the working copy, removing locks,
 
117
#   resuming unfinished operations, etc.
 
118
#       path:   The path to the directory to be cleaned
 
119
#
122
120
# TODO: Implement the following actions:
123
121
#   svnupdate (done?)
124
122
# TODO: Implement ZIP unpacking in putfiles (done?).
131
129
import os
132
130
import cStringIO
133
131
import shutil
 
132
import urllib
134
133
 
135
134
import pysvn
136
135
 
137
136
from ivle import (util, studpath, zip)
 
137
from ivle.fileservice_lib.exceptions import WillNotOverwrite
138
138
import ivle.conf
139
 
 
140
 
def get_login(_realm, existing_login, _may_save):
141
 
    """Callback function used by pysvn for authentication.
142
 
    realm, existing_login, _may_save: The 3 arguments passed by pysvn to
143
 
        callback_get_login.
144
 
        The following has been determined empirically, not from docs:
145
 
        existing_login will be the name of the user who owns the process on
146
 
        the first attempt, "" on subsequent attempts. We use this fact.
147
 
    """
148
 
    # Only provide credentials on the _first_ attempt.
149
 
    # If we're being asked again, then it means the credentials failed for
150
 
    # some reason and we should just fail. (This is not desirable, but it's
151
 
    # better than being asked an infinite number of times).
152
 
    return (existing_login != "", ivle.conf.login, ivle.conf.svn_pass, True)
153
 
 
154
 
# Make a Subversion client object
155
 
svnclient = pysvn.Client()
156
 
svnclient.callback_get_login = get_login
 
139
import ivle.svn
 
140
 
 
141
# Make a Subversion client object (which will log in with this user's
 
142
# credentials, upon request)
 
143
svnclient = ivle.svn.create_auth_svn_client(username=ivle.conf.login,
 
144
                                            password=ivle.conf.svn_pass)
157
145
svnclient.exception_style = 0               # Simple (string) exceptions
158
146
 
159
147
DEFAULT_LOGMESSAGE = "No log message supplied."
194
182
    except KeyError:
195
183
        # Default, just send an error but then continue
196
184
        raise ActionError("Unknown action")
197
 
    action(req, fields)
 
185
    return action(req, fields)
198
186
 
199
187
def actionpath_to_urlpath(req, path):
200
188
    """Determines the URL path (relative to the student home) upon which the
225
213
 
226
214
    Does not mutate req.
227
215
    """
228
 
    (_, _, r) = studpath.url_to_jailpaths(actionpath_to_urlpath(req, path))
 
216
    r = studpath.to_home_path(actionpath_to_urlpath(req, path))
229
217
    if r is None:
230
218
        raise ActionError("Invalid path")
231
219
    return r
263
251
    except shutil.Error:
264
252
        raise ActionError("Could not move the file specified")
265
253
 
 
254
def svn_movefile(req, frompath, topath, copy=False):
 
255
    """Performs an svn move, resolving filenames, checking for any errors,
 
256
    and throwing ActionErrors if necessary. Can also be used to do a copy
 
257
    operation instead.
 
258
 
 
259
    frompath and topath are straight paths from the client. Will be checked.
 
260
    """
 
261
    if frompath is None or topath is None:
 
262
        raise ActionError("Required field missing")
 
263
    frompath = actionpath_to_local(req, frompath)
 
264
    topath = actionpath_to_local(req, topath)
 
265
    if not os.path.exists(frompath):
 
266
        raise ActionError("The source file does not exist")
 
267
    if os.path.exists(topath):
 
268
        if frompath == topath:
 
269
            raise ActionError("Source and destination are the same")
 
270
        raise ActionError("A file already exists with that name")
 
271
 
 
272
    try:
 
273
        if copy:
 
274
            svnclient.copy(frompath, topath)
 
275
        else:
 
276
            svnclient.move(frompath, topath)
 
277
    except OSError:
 
278
        raise ActionError("Could not move the file specified")
 
279
    except pysvn.ClientError:
 
280
        raise ActionError("Could not move the file specified")  
 
281
 
 
282
 
266
283
### ACTIONS ###
267
284
 
268
285
def action_delete(req, fields):
300
317
    """
301
318
    frompath = fields.getfirst('from')
302
319
    topath = fields.getfirst('to')
303
 
    movefile(req, frompath, topath)
 
320
    svn = fields.getfirst('svn')
 
321
    if svn:
 
322
        svn_movefile(req, frompath, topath)
 
323
    else:
 
324
        movefile(req, frompath, topath)
304
325
 
305
326
def action_mkdir(req, fields):
306
327
    """Creates a directory with the given path.
370
391
 
371
392
    Reads fields: 'path', 'data' (file upload, multiple), 'unpack'
372
393
    """
373
 
 
374
394
    # Important: Data is "None" if the file submitted is empty.
375
395
    path = fields.getfirst('path')
376
396
    data = fields['data']
389
409
    for datum in data:
390
410
        # Each of the uploaded files
391
411
        filepath = os.path.join(path, datum.filename)
 
412
        filepath_local = studpath.to_home_path(filepath)
 
413
        if os.path.isdir(filepath_local):
 
414
            raise ActionError("A directory already exists "
 
415
                    + "with that name")
 
416
        else:
 
417
            if os.path.exists(filepath_local):
 
418
                raise ActionError("A file already exists with that name")
392
419
        filedata = datum.file
393
420
 
394
421
        if unpack and datum.filename.lower().endswith(".zip"):
398
425
            # filename)
399
426
            try:
400
427
                # First get the entire path (within jail)
401
 
                _, _, abspath = studpath.url_to_jailpaths(path)
 
428
                abspath = studpath.to_home_path(path)
402
429
                abspath = os.path.join(os.sep, abspath)
403
430
                zip.unzip(abspath, filedata)
404
431
            except (OSError, IOError):
405
432
                goterror = True
 
433
            except WillNotOverwrite, e:
 
434
                raise ActionError("File '" + e.filename + "' already exists.")
406
435
        else:
407
436
            # Not a zip file
408
 
            (_, _, filepath_local) = studpath.url_to_jailpaths(filepath)
 
437
            filepath_local = studpath.to_home_path(filepath)
409
438
            if filepath_local is None:
410
439
                raise ActionError("Invalid path")
411
440
 
444
473
    files = fields.getlist('file')
445
474
    if dst is None or src is None or mode is None:
446
475
        raise ActionError("Required field missing")
447
 
    if mode == "copy":
448
 
        copy = True
449
 
    elif mode == "move":
450
 
        copy = False
451
 
    else:
452
 
        raise ActionError("Invalid mode (must be 'copy' or 'move')")
 
476
 
453
477
    dst_local = actionpath_to_local(req, dst)
454
478
    if not os.path.isdir(dst_local):
455
479
        raise ActionError("dst is not a directory")
464
488
        # The destination is found by taking just the basename of the file
465
489
        topath = os.path.join(dst, os.path.basename(file))
466
490
        try:
467
 
            movefile(req, frompath, topath, copy)
 
491
            if mode == "copy":
 
492
                movefile(req, frompath, topath, True)
 
493
            elif mode == "move":
 
494
                movefile(req, frompath, topath, False)
 
495
            elif mode == "svncopy":
 
496
                svn_movefile(req, frompath, topath, True)
 
497
            elif mode == "svnmove":
 
498
                svn_movefile(req, frompath, topath, False)
 
499
            else:
 
500
                raise ActionError("Invalid mode (must be '(svn)copy' or '(svn)move')")
468
501
        except ActionError, message:
469
502
            # Store the error for later; we want to copy as many as possible
470
503
            if errormsg is None:
488
521
    Reads fields: 'path'
489
522
    """
490
523
    paths = fields.getlist('path')
491
 
    user = studpath.url_to_local(req.path)[0]
 
524
    user = util.split_path(req.path)[0]
492
525
    homedir = "/home/%s" % user
493
526
    if len(paths):
494
527
        paths = map(lambda path: actionpath_to_local(req, path), paths)
495
528
    else:
496
 
        paths = [studpath.url_to_jailpaths(req.path)[2]]
 
529
        paths = [studpath.to_home_path(req.path)]
497
530
 
498
531
    # Set all the dirs in home dir world browsable (o+r,o+x)
499
532
    #FIXME: Should really only do those in the direct path not all of the 
523
556
    if len(paths):
524
557
        paths = map(lambda path: actionpath_to_local(req, path), paths)
525
558
    else:
526
 
        paths = [studpath.url_to_jailpaths(req.path)[2]]
 
559
        paths = [studpath.to_home_path(req.path)]
527
560
 
528
561
    try:
529
562
        for path in paths:
543
576
    Reads fields: 'path' (multiple)
544
577
    """
545
578
    paths = fields.getlist('path')
546
 
    paths = map(lambda path: actionpath_to_local(req, path), paths)
 
579
    paths = map(lambda path: actionpath_to_local(req, path).decode('utf-8'),
 
580
                paths)
547
581
 
548
582
    try:
549
583
        svnclient.add(paths, recurse=True, force=True)
556
590
    Reads fields: 'path' (multiple)
557
591
    """
558
592
    paths = fields.getlist('path')
559
 
    paths = map(lambda path: actionpath_to_local(req, path), paths)
 
593
    paths = map(lambda path: actionpath_to_local(req, path).decode('utf-8'),
 
594
                paths)
560
595
 
561
596
    try:
562
597
        svnclient.remove(paths, force=True)
566
601
def action_svnupdate(req, fields):
567
602
    """Performs a "svn update" to each file specified.
568
603
 
569
 
    Reads fields: 'path'
 
604
    Reads fields: 'path' and 'revision'
570
605
    """
571
606
    path = fields.getfirst('path')
 
607
    revision = fields.getfirst('revision')
572
608
    if path is None:
573
609
        raise ActionError("Required field missing")
574
 
    path = actionpath_to_local(req, path)
 
610
    if revision is None:
 
611
        revision = pysvn.Revision( pysvn.opt_revision_kind.head )
 
612
    else:
 
613
        try:
 
614
            revision = pysvn.Revision(pysvn.opt_revision_kind.number,
 
615
                    int(revision))
 
616
        except ValueError, e:
 
617
            raise ActionError("Bad revision number: '%s'"%revision,)
 
618
    path = actionpath_to_local(req, path).decode('utf-8')
575
619
 
576
620
    try:
577
 
        svnclient.update(path, recurse=True)
 
621
        svnclient.update(path, recurse=True, revision=revision)
578
622
    except pysvn.ClientError, e:
579
623
        raise ActionError(str(e))
580
624
 
586
630
    path = fields.getfirst('path')
587
631
    if path is None:
588
632
        raise ActionError("Required field missing")
589
 
    path = actionpath_to_local(req, path)
 
633
    path = actionpath_to_local(req, path).decode('utf-8')
590
634
 
591
635
    try:
592
636
        svnclient.resolved(path, recurse=True)
599
643
    Reads fields: 'path' (multiple)
600
644
    """
601
645
    paths = fields.getlist('path')
602
 
    paths = map(lambda path: actionpath_to_local(req, path), paths)
 
646
    paths = map(lambda path: actionpath_to_local(req, path).decode('utf-8'),
 
647
                paths)
603
648
 
604
649
    try:
605
650
        svnclient.revert(paths, recurse=True)
606
651
    except pysvn.ClientError, e:
607
652
        raise ActionError(str(e))
608
653
 
609
 
def action_svnpublish(req, fields):
610
 
    """Sets svn property "ivle:published" on each file specified.
611
 
    Should only be called on directories (only effective on directories
612
 
    anyway).
613
 
 
614
 
    Reads fields: 'path'
615
 
 
616
 
    XXX Currently unused by the client (calls action_publish instead, which
617
 
    has a completely different publishing model).
618
 
    """
619
 
    paths = fields.getlist('path')
620
 
    if len(paths):
621
 
        paths = map(lambda path: actionpath_to_local(req, path), paths)
622
 
    else:
623
 
        paths = [studpath.url_to_jailpaths(req.path)[2]]
624
 
 
625
 
    try:
626
 
        for path in paths:
627
 
            # Note: Property value doesn't matter
628
 
            svnclient.propset("ivle:published", "", path, recurse=False)
629
 
    except pysvn.ClientError, e:
630
 
        raise ActionError("Directory could not be published")
631
 
 
632
 
def action_svnunpublish(req, fields):
633
 
    """Deletes svn property "ivle:published" on each file specified.
634
 
 
635
 
    Reads fields: 'path'
636
 
 
637
 
    XXX Currently unused by the client (calls action_unpublish instead, which
638
 
    has a completely different publishing model).
639
 
    """
640
 
    paths = fields.getlist('path')
641
 
    paths = map(lambda path: actionpath_to_local(req, path), paths)
642
 
 
643
 
    try:
644
 
        for path in paths:
645
 
            svnclient.propdel("ivle:published", path, recurse=False)
646
 
    except pysvn.ClientError, e:
647
 
        raise ActionError("Directory could not be unpublished")
648
 
 
649
654
def action_svncommit(req, fields):
650
655
    """Performs a "svn commit" to each file specified.
651
656
 
652
657
    Reads fields: 'path' (multiple), 'logmsg' (optional)
653
658
    """
654
659
    paths = fields.getlist('path')
655
 
    paths = map(lambda path: actionpath_to_local(req, str(path)), paths)
656
 
    logmsg = str(fields.getfirst('logmsg', DEFAULT_LOGMESSAGE))
 
660
    if len(paths):
 
661
        paths = map(lambda path:actionpath_to_local(req,path).decode('utf-8'),
 
662
                    paths)
 
663
    else:
 
664
        paths = [studpath.to_home_path(req.path).decode('utf-8')]
 
665
    logmsg = str(fields.getfirst('logmsg',
 
666
                 DEFAULT_LOGMESSAGE)).decode('utf-8')
657
667
    if logmsg == '': logmsg = DEFAULT_LOGMESSAGE
658
668
 
659
669
    try:
669
679
    paths = fields.getlist('path')
670
680
    if len(paths) != 2:
671
681
        raise ActionError("usage: svncheckout url local-path")
672
 
    url = ivle.conf.svn_addr + "/" + paths[0]
 
682
    url = ivle.conf.svn_addr + "/" + urllib.quote(paths[0])
673
683
    local_path = actionpath_to_local(req, str(paths[1]))
 
684
    url = url.decode('utf-8')
 
685
    local_path = local_path.decode('utf-8')
674
686
    try:
675
 
        svnclient.callback_get_login = get_login
676
687
        svnclient.checkout(url, local_path, recurse=True)
677
688
    except pysvn.ClientError, e:
678
689
        raise ActionError(str(e))
684
695
    """
685
696
    path = fields.getfirst('path')
686
697
    logmsg = fields.getfirst('logmsg')
687
 
    url = ivle.conf.svn_addr + "/" + path
 
698
    url = (ivle.conf.svn_addr + "/" + urllib.quote(path)).decode('utf-8')
688
699
    try:
689
 
        svnclient.callback_get_login = get_login
690
700
        svnclient.mkdir(url, log_message=logmsg)
691
701
    except pysvn.ClientError, e:
692
702
        raise ActionError(str(e))
694
704
def action_svnrepostat(req, fields):
695
705
    """Discovers whether a path exists in a repo under the IVLE SVN root.
696
706
 
 
707
    If it does exist, returns a dict containing its metadata.
 
708
 
697
709
    Reads fields: 'path'
698
710
    """
699
711
    path = fields.getfirst('path')
700
 
    url = ivle.conf.svn_addr + "/" + path
701
 
    svnclient.exception_style = 1 
 
712
    url = (ivle.conf.svn_addr + "/" + urllib.quote(path)).decode('utf-8')
 
713
    svnclient.exception_style = 1
702
714
 
703
715
    try:
704
 
        svnclient.callback_get_login = get_login
705
 
        svnclient.info2(url)
 
716
        info = svnclient.info2(url,
 
717
            revision=pysvn.Revision(pysvn.opt_revision_kind.head))[0][1]
 
718
        return {'svnrevision': info['rev'].number
 
719
                  if info['rev'] and
 
720
                     info['rev'].kind == pysvn.opt_revision_kind.number
 
721
                  else None}
706
722
    except pysvn.ClientError, e:
707
723
        # Error code 170000 means ENOENT in this revision.
708
724
        if e[1][0][1] == 170000:
709
 
            raise util.IVLEError(404, 'The specified repository path does not exist')
 
725
            req.status = 404
 
726
            raise ActionError('The specified repository path does not exist')
710
727
        else:
711
728
            raise ActionError(str(e[0]))
712
729
 
 
730
 
 
731
def action_svncleanup(req, fields):
 
732
    """Recursively clean up the working copy, removing locks, resuming 
 
733
    unfinished operations, etc.
 
734
        path:   The path to be cleaned"""
 
735
 
 
736
    path = fields.getfirst('path')
 
737
    if path is None:
 
738
        raise ActionError("Required field missing")
 
739
    path = actionpath_to_local(req, path).decode('utf-8')
 
740
 
 
741
    try:
 
742
        svnclient.cleanup(path)
 
743
    except pysvn.ClientError, e:
 
744
        raise ActionError(str(e))
 
745
 
 
746
 
713
747
# Table of all action functions #
714
748
# Each function has the interface f(req, fields).
715
749
 
728
762
    "svnupdate" : action_svnupdate,
729
763
    "svnresolved" : action_svnresolved,
730
764
    "svnrevert" : action_svnrevert,
731
 
    "svnpublish" : action_svnpublish,
732
 
    "svnunpublish" : action_svnunpublish,
733
765
    "svncommit" : action_svncommit,
734
766
    "svncheckout" : action_svncheckout,
735
767
    "svnrepomkdir" : action_svnrepomkdir,
736
768
    "svnrepostat" : action_svnrepostat,
 
769
    "svncleanup" : action_svncleanup,
737
770
}