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
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
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
94
# revision: The revision number to update to. If not provided this
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).
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
122
120
# TODO: Implement the following actions:
123
121
# svnupdate (done?)
124
122
# TODO: Implement ZIP unpacking in putfiles (done?).
137
136
from ivle import (util, studpath, zip)
137
from ivle.fileservice_lib.exceptions import WillNotOverwrite
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
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.
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)
154
# Make a Subversion client object
155
svnclient = pysvn.Client()
156
svnclient.callback_get_login = get_login
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
159
147
DEFAULT_LOGMESSAGE = "No log message supplied."
263
251
except shutil.Error:
264
252
raise ActionError("Could not move the file specified")
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
259
frompath and topath are straight paths from the client. Will be checked.
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")
274
svnclient.copy(frompath, topath)
276
svnclient.move(frompath, topath)
278
raise ActionError("Could not move the file specified")
279
except pysvn.ClientError:
280
raise ActionError("Could not move the file specified")
268
285
def action_delete(req, fields):
301
318
frompath = fields.getfirst('from')
302
319
topath = fields.getfirst('to')
303
movefile(req, frompath, topath)
320
svn = fields.getfirst('svn')
322
svn_movefile(req, frompath, topath)
324
movefile(req, frompath, topath)
305
326
def action_mkdir(req, fields):
306
327
"""Creates a directory with the given path.
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 "
417
if os.path.exists(filepath_local):
418
raise ActionError("A file already exists with that name")
392
419
filedata = datum.file
394
421
if unpack and datum.filename.lower().endswith(".zip"):
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):
433
except WillNotOverwrite, e:
434
raise ActionError("File '" + e.filename + "' already exists.")
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")
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")
452
raise ActionError("Invalid mode (must be 'copy' or 'move')")
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))
467
movefile(req, frompath, topath, copy)
492
movefile(req, frompath, topath, True)
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)
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'
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
494
527
paths = map(lambda path: actionpath_to_local(req, path), paths)
496
paths = [studpath.url_to_jailpaths(req.path)[2]]
529
paths = [studpath.to_home_path(req.path)]
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
543
576
Reads fields: 'path' (multiple)
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'),
549
583
svnclient.add(paths, recurse=True, force=True)
556
590
Reads fields: 'path' (multiple)
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'),
562
597
svnclient.remove(paths, force=True)
566
601
def action_svnupdate(req, fields):
567
602
"""Performs a "svn update" to each file specified.
604
Reads fields: 'path' and 'revision'
571
606
path = fields.getfirst('path')
607
revision = fields.getfirst('revision')
573
609
raise ActionError("Required field missing")
574
path = actionpath_to_local(req, path)
611
revision = pysvn.Revision( pysvn.opt_revision_kind.head )
614
revision = pysvn.Revision(pysvn.opt_revision_kind.number,
616
except ValueError, e:
617
raise ActionError("Bad revision number: '%s'"%revision,)
618
path = actionpath_to_local(req, path).decode('utf-8')
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))
599
643
Reads fields: 'path' (multiple)
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'),
605
650
svnclient.revert(paths, recurse=True)
606
651
except pysvn.ClientError, e:
607
652
raise ActionError(str(e))
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
616
XXX Currently unused by the client (calls action_publish instead, which
617
has a completely different publishing model).
619
paths = fields.getlist('path')
621
paths = map(lambda path: actionpath_to_local(req, path), paths)
623
paths = [studpath.url_to_jailpaths(req.path)[2]]
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")
632
def action_svnunpublish(req, fields):
633
"""Deletes svn property "ivle:published" on each file specified.
637
XXX Currently unused by the client (calls action_unpublish instead, which
638
has a completely different publishing model).
640
paths = fields.getlist('path')
641
paths = map(lambda path: actionpath_to_local(req, path), paths)
645
svnclient.propdel("ivle:published", path, recurse=False)
646
except pysvn.ClientError, e:
647
raise ActionError("Directory could not be unpublished")
649
654
def action_svncommit(req, fields):
650
655
"""Performs a "svn commit" to each file specified.
652
657
Reads fields: 'path' (multiple), 'logmsg' (optional)
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))
661
paths = map(lambda path:actionpath_to_local(req,path).decode('utf-8'),
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
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')
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))
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')
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.
707
If it does exist, returns a dict containing its metadata.
697
709
Reads fields: 'path'
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
704
svnclient.callback_get_login = get_login
716
info = svnclient.info2(url,
717
revision=pysvn.Revision(pysvn.opt_revision_kind.head))[0][1]
718
return {'svnrevision': info['rev'].number
720
info['rev'].kind == pysvn.opt_revision_kind.number
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')
726
raise ActionError('The specified repository path does not exist')
711
728
raise ActionError(str(e[0]))
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"""
736
path = fields.getfirst('path')
738
raise ActionError("Required field missing")
739
path = actionpath_to_local(req, path).decode('utf-8')
742
svnclient.cleanup(path)
743
except pysvn.ClientError, e:
744
raise ActionError(str(e))
713
747
# Table of all action functions #
714
748
# Each function has the interface f(req, fields).
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,