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

« back to all changes in this revision

Viewing changes to ivle/fileservice_lib/action.py

  • Committer: me at id
  • Date: 2009-01-15 05:53:45 UTC
  • mto: This revision was merged to the branch mainline in revision 1090.
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:branches%2Fstorm:1161
bin/ivle-showenrolment: Switch to ivle.database.User.enrolments from
    ivle.db.get_enrolment, removing the dependency on ivle.db.

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
 
#       revision: The revision number to update to. If not provided this
95
 
#               defaults to HEAD.
 
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.
96
102
#
97
103
# action=svncommit: Commit a file(s) or directory(s) to the repository.
98
104
#       path:   The path to the file or directory to be committed. Can be
113
119
#       path:   The path to the directory to be checked (under the IVLE
114
120
#               repository base).
115
121
#
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
 
#
120
122
# TODO: Implement the following actions:
121
123
#   svnupdate (done?)
122
124
# TODO: Implement ZIP unpacking in putfiles (done?).
129
131
import os
130
132
import cStringIO
131
133
import shutil
132
 
import urllib
133
134
 
134
135
import pysvn
135
136
 
136
137
from ivle import (util, studpath, zip)
137
 
from ivle.fileservice_lib.exceptions import WillNotOverwrite
138
138
import ivle.conf
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)
 
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 != "", str(ivle.conf.login),
 
153
                                  str(ivle.conf.svn_pass), True)
 
154
 
 
155
# Make a Subversion client object
 
156
svnclient = pysvn.Client()
 
157
svnclient.callback_get_login = get_login
145
158
svnclient.exception_style = 0               # Simple (string) exceptions
146
159
 
147
160
DEFAULT_LOGMESSAGE = "No log message supplied."
182
195
    except KeyError:
183
196
        # Default, just send an error but then continue
184
197
        raise ActionError("Unknown action")
185
 
    return action(req, fields)
 
198
    action(req, fields)
186
199
 
187
200
def actionpath_to_urlpath(req, path):
188
201
    """Determines the URL path (relative to the student home) upon which the
213
226
 
214
227
    Does not mutate req.
215
228
    """
216
 
    r = studpath.to_home_path(actionpath_to_urlpath(req, path))
 
229
    (_, _, r) = studpath.url_to_jailpaths(actionpath_to_urlpath(req, path))
217
230
    if r is None:
218
231
        raise ActionError("Invalid path")
219
232
    return r
251
264
    except shutil.Error:
252
265
        raise ActionError("Could not move the file specified")
253
266
 
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
 
 
283
267
### ACTIONS ###
284
268
 
285
269
def action_delete(req, fields):
387
371
 
388
372
    Reads fields: 'path', 'data' (file upload, multiple), 'unpack'
389
373
    """
 
374
 
390
375
    # Important: Data is "None" if the file submitted is empty.
391
376
    path = fields.getfirst('path')
392
377
    data = fields['data']
405
390
    for datum in data:
406
391
        # Each of the uploaded files
407
392
        filepath = os.path.join(path, datum.filename)
408
 
        filepath_local = studpath.to_home_path(filepath)
409
 
        if os.path.isdir(filepath_local):
410
 
            raise ActionError("A directory already exists "
411
 
                    + "with that name")
412
 
        else:
413
 
            if os.path.exists(filepath_local):
414
 
                raise ActionError("A file already exists with that name")
415
393
        filedata = datum.file
416
394
 
417
395
        if unpack and datum.filename.lower().endswith(".zip"):
421
399
            # filename)
422
400
            try:
423
401
                # First get the entire path (within jail)
424
 
                abspath = studpath.to_home_path(path)
 
402
                _, _, abspath = studpath.url_to_jailpaths(path)
425
403
                abspath = os.path.join(os.sep, abspath)
426
404
                zip.unzip(abspath, filedata)
427
405
            except (OSError, IOError):
428
406
                goterror = True
429
 
            except WillNotOverwrite, e:
430
 
                raise ActionError("File '" + e.filename + "' already exists.")
431
407
        else:
432
408
            # Not a zip file
433
 
            filepath_local = studpath.to_home_path(filepath)
 
409
            (_, _, filepath_local) = studpath.url_to_jailpaths(filepath)
434
410
            if filepath_local is None:
435
411
                raise ActionError("Invalid path")
436
412
 
469
445
    files = fields.getlist('file')
470
446
    if dst is None or src is None or mode is None:
471
447
        raise ActionError("Required field missing")
472
 
 
 
448
    if mode == "copy":
 
449
        copy = True
 
450
    elif mode == "move":
 
451
        copy = False
 
452
    else:
 
453
        raise ActionError("Invalid mode (must be 'copy' or 'move')")
473
454
    dst_local = actionpath_to_local(req, dst)
474
455
    if not os.path.isdir(dst_local):
475
456
        raise ActionError("dst is not a directory")
484
465
        # The destination is found by taking just the basename of the file
485
466
        topath = os.path.join(dst, os.path.basename(file))
486
467
        try:
487
 
            if mode == "copy":
488
 
                movefile(req, frompath, topath, True)
489
 
            elif mode == "move":
490
 
                movefile(req, frompath, topath, False)
491
 
            elif mode == "svncopy":
492
 
                svn_movefile(req, frompath, topath, True)
493
 
            elif mode == "svnmove":
494
 
                svn_movefile(req, frompath, topath, False)
495
 
            else:
496
 
                raise ActionError("Invalid mode (must be '(svn)copy' or '(svn)move')")
 
468
            movefile(req, frompath, topath, copy)
497
469
        except ActionError, message:
498
470
            # Store the error for later; we want to copy as many as possible
499
471
            if errormsg is None:
517
489
    Reads fields: 'path'
518
490
    """
519
491
    paths = fields.getlist('path')
520
 
    user = util.split_path(req.path)[0]
 
492
    user = studpath.url_to_local(req.path)[0]
521
493
    homedir = "/home/%s" % user
522
494
    if len(paths):
523
495
        paths = map(lambda path: actionpath_to_local(req, path), paths)
524
496
    else:
525
 
        paths = [studpath.to_home_path(req.path)]
 
497
        paths = [studpath.url_to_jailpaths(req.path)[2]]
526
498
 
527
499
    # Set all the dirs in home dir world browsable (o+r,o+x)
528
500
    #FIXME: Should really only do those in the direct path not all of the 
552
524
    if len(paths):
553
525
        paths = map(lambda path: actionpath_to_local(req, path), paths)
554
526
    else:
555
 
        paths = [studpath.to_home_path(req.path)]
 
527
        paths = [studpath.url_to_jailpaths(req.path)[2]]
556
528
 
557
529
    try:
558
530
        for path in paths:
572
544
    Reads fields: 'path' (multiple)
573
545
    """
574
546
    paths = fields.getlist('path')
575
 
    paths = map(lambda path: actionpath_to_local(req, path).decode('utf-8'),
576
 
                paths)
 
547
    paths = map(lambda path: actionpath_to_local(req, path), paths)
577
548
 
578
549
    try:
579
550
        svnclient.add(paths, recurse=True, force=True)
586
557
    Reads fields: 'path' (multiple)
587
558
    """
588
559
    paths = fields.getlist('path')
589
 
    paths = map(lambda path: actionpath_to_local(req, path).decode('utf-8'),
590
 
                paths)
 
560
    paths = map(lambda path: actionpath_to_local(req, path), paths)
591
561
 
592
562
    try:
593
563
        svnclient.remove(paths, force=True)
597
567
def action_svnupdate(req, fields):
598
568
    """Performs a "svn update" to each file specified.
599
569
 
600
 
    Reads fields: 'path' and 'revision'
 
570
    Reads fields: 'path'
601
571
    """
602
572
    path = fields.getfirst('path')
603
 
    revision = fields.getfirst('revision')
604
573
    if path is None:
605
574
        raise ActionError("Required field missing")
606
 
    if revision is None:
607
 
        revision = pysvn.Revision( pysvn.opt_revision_kind.head )
608
 
    else:
609
 
        try:
610
 
            revision = pysvn.Revision(pysvn.opt_revision_kind.number,
611
 
                    int(revision))
612
 
        except ValueError, e:
613
 
            raise ActionError("Bad revision number: '%s'"%revision,)
614
 
    path = actionpath_to_local(req, path).decode('utf-8')
 
575
    path = actionpath_to_local(req, path)
615
576
 
616
577
    try:
617
 
        svnclient.update(path, recurse=True, revision=revision)
 
578
        svnclient.update(path, recurse=True)
618
579
    except pysvn.ClientError, e:
619
580
        raise ActionError(str(e))
620
581
 
626
587
    path = fields.getfirst('path')
627
588
    if path is None:
628
589
        raise ActionError("Required field missing")
629
 
    path = actionpath_to_local(req, path).decode('utf-8')
 
590
    path = actionpath_to_local(req, path)
630
591
 
631
592
    try:
632
593
        svnclient.resolved(path, recurse=True)
639
600
    Reads fields: 'path' (multiple)
640
601
    """
641
602
    paths = fields.getlist('path')
642
 
    paths = map(lambda path: actionpath_to_local(req, path).decode('utf-8'),
643
 
                paths)
 
603
    paths = map(lambda path: actionpath_to_local(req, path), paths)
644
604
 
645
605
    try:
646
606
        svnclient.revert(paths, recurse=True)
647
607
    except pysvn.ClientError, e:
648
608
        raise ActionError(str(e))
649
609
 
 
610
def action_svnpublish(req, fields):
 
611
    """Sets svn property "ivle:published" on each file specified.
 
612
    Should only be called on directories (only effective on directories
 
613
    anyway).
 
614
 
 
615
    Reads fields: 'path'
 
616
 
 
617
    XXX Currently unused by the client (calls action_publish instead, which
 
618
    has a completely different publishing model).
 
619
    """
 
620
    paths = fields.getlist('path')
 
621
    if len(paths):
 
622
        paths = map(lambda path: actionpath_to_local(req, path), paths)
 
623
    else:
 
624
        paths = [studpath.url_to_jailpaths(req.path)[2]]
 
625
 
 
626
    try:
 
627
        for path in paths:
 
628
            # Note: Property value doesn't matter
 
629
            svnclient.propset("ivle:published", "", path, recurse=False)
 
630
    except pysvn.ClientError, e:
 
631
        raise ActionError("Directory could not be published")
 
632
 
 
633
def action_svnunpublish(req, fields):
 
634
    """Deletes svn property "ivle:published" on each file specified.
 
635
 
 
636
    Reads fields: 'path'
 
637
 
 
638
    XXX Currently unused by the client (calls action_unpublish instead, which
 
639
    has a completely different publishing model).
 
640
    """
 
641
    paths = fields.getlist('path')
 
642
    paths = map(lambda path: actionpath_to_local(req, path), paths)
 
643
 
 
644
    try:
 
645
        for path in paths:
 
646
            svnclient.propdel("ivle:published", path, recurse=False)
 
647
    except pysvn.ClientError, e:
 
648
        raise ActionError("Directory could not be unpublished")
 
649
 
650
650
def action_svncommit(req, fields):
651
651
    """Performs a "svn commit" to each file specified.
652
652
 
653
653
    Reads fields: 'path' (multiple), 'logmsg' (optional)
654
654
    """
655
655
    paths = fields.getlist('path')
656
 
    if len(paths):
657
 
        paths = map(lambda path:actionpath_to_local(req,path).decode('utf-8'),
658
 
                    paths)
659
 
    else:
660
 
        paths = [studpath.to_home_path(req.path).decode('utf-8')]
661
 
    logmsg = str(fields.getfirst('logmsg',
662
 
                 DEFAULT_LOGMESSAGE)).decode('utf-8')
 
656
    paths = map(lambda path: actionpath_to_local(req, str(path)), paths)
 
657
    logmsg = str(fields.getfirst('logmsg', DEFAULT_LOGMESSAGE))
663
658
    if logmsg == '': logmsg = DEFAULT_LOGMESSAGE
664
659
 
665
660
    try:
675
670
    paths = fields.getlist('path')
676
671
    if len(paths) != 2:
677
672
        raise ActionError("usage: svncheckout url local-path")
678
 
    url = ivle.conf.svn_addr + "/" + urllib.quote(paths[0])
 
673
    url = ivle.conf.svn_addr + "/" + paths[0]
679
674
    local_path = actionpath_to_local(req, str(paths[1]))
680
 
    url = url.decode('utf-8')
681
 
    local_path = local_path.decode('utf-8')
682
675
    try:
 
676
        svnclient.callback_get_login = get_login
683
677
        svnclient.checkout(url, local_path, recurse=True)
684
678
    except pysvn.ClientError, e:
685
679
        raise ActionError(str(e))
691
685
    """
692
686
    path = fields.getfirst('path')
693
687
    logmsg = fields.getfirst('logmsg')
694
 
    url = (ivle.conf.svn_addr + "/" + urllib.quote(path)).decode('utf-8')
 
688
    url = ivle.conf.svn_addr + "/" + path
695
689
    try:
 
690
        svnclient.callback_get_login = get_login
696
691
        svnclient.mkdir(url, log_message=logmsg)
697
692
    except pysvn.ClientError, e:
698
693
        raise ActionError(str(e))
700
695
def action_svnrepostat(req, fields):
701
696
    """Discovers whether a path exists in a repo under the IVLE SVN root.
702
697
 
703
 
    If it does exist, returns a dict containing its metadata.
704
 
 
705
698
    Reads fields: 'path'
706
699
    """
707
700
    path = fields.getfirst('path')
708
 
    url = (ivle.conf.svn_addr + "/" + urllib.quote(path)).decode('utf-8')
709
 
    svnclient.exception_style = 1
 
701
    url = ivle.conf.svn_addr + "/" + path
 
702
    svnclient.exception_style = 1 
710
703
 
711
704
    try:
712
 
        info = svnclient.info2(url,
713
 
            revision=pysvn.Revision(pysvn.opt_revision_kind.head))[0][1]
714
 
        return {'svnrevision': info['rev'].number
715
 
                  if info['rev'] and
716
 
                     info['rev'].kind == pysvn.opt_revision_kind.number
717
 
                  else None}
 
705
        svnclient.callback_get_login = get_login
 
706
        svnclient.info2(url)
718
707
    except pysvn.ClientError, e:
719
708
        # Error code 170000 means ENOENT in this revision.
720
709
        if e[1][0][1] == 170000:
722
711
        else:
723
712
            raise ActionError(str(e[0]))
724
713
 
725
 
 
726
 
def action_svncleanup(req, fields):
727
 
    """Recursively clean up the working copy, removing locks, resuming 
728
 
    unfinished operations, etc.
729
 
        path:   The path to be cleaned"""
730
 
 
731
 
    path = fields.getfirst('path')
732
 
    if path is None:
733
 
        raise ActionError("Required field missing")
734
 
    path = actionpath_to_local(req, path).decode('utf-8')
735
 
 
736
 
    try:
737
 
        svnclient.cleanup(path)
738
 
    except pysvn.ClientError, e:
739
 
        raise ActionError(str(e))
740
 
 
741
 
 
742
714
# Table of all action functions #
743
715
# Each function has the interface f(req, fields).
744
716
 
757
729
    "svnupdate" : action_svnupdate,
758
730
    "svnresolved" : action_svnresolved,
759
731
    "svnrevert" : action_svnrevert,
 
732
    "svnpublish" : action_svnpublish,
 
733
    "svnunpublish" : action_svnunpublish,
760
734
    "svncommit" : action_svncommit,
761
735
    "svncheckout" : action_svncheckout,
762
736
    "svnrepomkdir" : action_svnrepomkdir,
763
737
    "svnrepostat" : action_svnrepostat,
764
 
    "svncleanup" : action_svncleanup,
765
738
}