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
94
# revision: The revision number to update to. If not provided this
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
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).
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
120
122
# TODO: Implement the following actions:
121
123
# svnupdate (done?)
122
124
# TODO: Implement ZIP unpacking in putfiles (done?).
136
137
from ivle import (util, studpath, zip)
137
from ivle.fileservice_lib.exceptions import WillNotOverwrite
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)
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 != "", str(ivle.conf.login),
153
str(ivle.conf.svn_pass), True)
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
147
160
DEFAULT_LOGMESSAGE = "No log message supplied."
251
264
except shutil.Error:
252
265
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")
285
269
def action_delete(req, fields):
318
302
frompath = fields.getfirst('from')
319
303
topath = fields.getfirst('to')
320
svn = fields.getfirst('svn')
322
svn_movefile(req, frompath, topath)
324
movefile(req, frompath, topath)
304
movefile(req, frompath, topath)
326
306
def action_mkdir(req, fields):
327
307
"""Creates a directory with the given path.
409
390
for datum in data:
410
391
# Each of the uploaded files
411
392
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")
419
393
filedata = datum.file
421
395
if unpack and datum.filename.lower().endswith(".zip"):
427
401
# First get the entire path (within jail)
428
abspath = studpath.to_home_path(path)
402
_, _, abspath = studpath.url_to_jailpaths(path)
429
403
abspath = os.path.join(os.sep, abspath)
430
404
zip.unzip(abspath, filedata)
431
405
except (OSError, IOError):
433
except WillNotOverwrite, e:
434
raise ActionError("File '" + e.filename + "' already exists.")
437
filepath_local = studpath.to_home_path(filepath)
409
(_, _, filepath_local) = studpath.url_to_jailpaths(filepath)
438
410
if filepath_local is None:
439
411
raise ActionError("Invalid path")
473
445
files = fields.getlist('file')
474
446
if dst is None or src is None or mode is None:
475
447
raise ActionError("Required field missing")
453
raise ActionError("Invalid mode (must be 'copy' or 'move')")
477
454
dst_local = actionpath_to_local(req, dst)
478
455
if not os.path.isdir(dst_local):
479
456
raise ActionError("dst is not a directory")
488
465
# The destination is found by taking just the basename of the file
489
466
topath = os.path.join(dst, os.path.basename(file))
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
movefile(req, frompath, topath, copy)
501
469
except ActionError, message:
502
470
# Store the error for later; we want to copy as many as possible
503
471
if errormsg is None:
521
489
Reads fields: 'path'
523
491
paths = fields.getlist('path')
524
user = util.split_path(req.path)[0]
492
user = studpath.url_to_local(req.path)[0]
525
493
homedir = "/home/%s" % user
527
495
paths = map(lambda path: actionpath_to_local(req, path), paths)
529
paths = [studpath.to_home_path(req.path)]
497
paths = [studpath.url_to_jailpaths(req.path)[2]]
531
499
# Set all the dirs in home dir world browsable (o+r,o+x)
532
500
#FIXME: Should really only do those in the direct path not all of the
576
544
Reads fields: 'path' (multiple)
578
546
paths = fields.getlist('path')
579
paths = map(lambda path: actionpath_to_local(req, path).decode('utf-8'),
547
paths = map(lambda path: actionpath_to_local(req, path), paths)
583
550
svnclient.add(paths, recurse=True, force=True)
590
557
Reads fields: 'path' (multiple)
592
559
paths = fields.getlist('path')
593
paths = map(lambda path: actionpath_to_local(req, path).decode('utf-8'),
560
paths = map(lambda path: actionpath_to_local(req, path), paths)
597
563
svnclient.remove(paths, force=True)
601
567
def action_svnupdate(req, fields):
602
568
"""Performs a "svn update" to each file specified.
604
Reads fields: 'path' and 'revision'
606
572
path = fields.getfirst('path')
607
revision = fields.getfirst('revision')
609
574
raise ActionError("Required field missing")
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')
575
path = actionpath_to_local(req, path)
621
svnclient.update(path, recurse=True, revision=revision)
578
svnclient.update(path, recurse=True)
622
579
except pysvn.ClientError, e:
623
580
raise ActionError(str(e))
643
600
Reads fields: 'path' (multiple)
645
602
paths = fields.getlist('path')
646
paths = map(lambda path: actionpath_to_local(req, path).decode('utf-8'),
603
paths = map(lambda path: actionpath_to_local(req, path), paths)
650
606
svnclient.revert(paths, recurse=True)
651
607
except pysvn.ClientError, e:
652
608
raise ActionError(str(e))
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
617
XXX Currently unused by the client (calls action_publish instead, which
618
has a completely different publishing model).
620
paths = fields.getlist('path')
622
paths = map(lambda path: actionpath_to_local(req, path), paths)
624
paths = [studpath.url_to_jailpaths(req.path)[2]]
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")
633
def action_svnunpublish(req, fields):
634
"""Deletes svn property "ivle:published" on each file specified.
638
XXX Currently unused by the client (calls action_unpublish instead, which
639
has a completely different publishing model).
641
paths = fields.getlist('path')
642
paths = map(lambda path: actionpath_to_local(req, path), paths)
646
svnclient.propdel("ivle:published", path, recurse=False)
647
except pysvn.ClientError, e:
648
raise ActionError("Directory could not be unpublished")
654
650
def action_svncommit(req, fields):
655
651
"""Performs a "svn commit" to each file specified.
657
653
Reads fields: 'path' (multiple), 'logmsg' (optional)
659
655
paths = fields.getlist('path')
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')
656
paths = map(lambda path: actionpath_to_local(req, str(path)), paths)
657
logmsg = str(fields.getfirst('logmsg', DEFAULT_LOGMESSAGE))
667
658
if logmsg == '': logmsg = DEFAULT_LOGMESSAGE
679
670
paths = fields.getlist('path')
680
671
if len(paths) != 2:
681
672
raise ActionError("usage: svncheckout url local-path")
682
url = ivle.conf.svn_addr + "/" + urllib.quote(paths[0])
673
url = ivle.conf.svn_addr + "/" + paths[0]
683
674
local_path = actionpath_to_local(req, str(paths[1]))
684
url = url.decode('utf-8')
685
local_path = local_path.decode('utf-8')
676
svnclient.callback_get_login = get_login
687
677
svnclient.checkout(url, local_path, recurse=True)
688
678
except pysvn.ClientError, e:
689
679
raise ActionError(str(e))
696
686
path = fields.getfirst('path')
697
687
logmsg = fields.getfirst('logmsg')
698
url = (ivle.conf.svn_addr + "/" + urllib.quote(path)).decode('utf-8')
688
url = ivle.conf.svn_addr + "/" + path
690
svnclient.callback_get_login = get_login
700
691
svnclient.mkdir(url, log_message=logmsg)
701
692
except pysvn.ClientError, e:
702
693
raise ActionError(str(e))
704
695
def action_svnrepostat(req, fields):
705
696
"""Discovers whether a path exists in a repo under the IVLE SVN root.
707
If it does exist, returns a dict containing its metadata.
709
698
Reads fields: 'path'
711
700
path = fields.getfirst('path')
712
url = (ivle.conf.svn_addr + "/" + urllib.quote(path)).decode('utf-8')
713
svnclient.exception_style = 1
701
url = ivle.conf.svn_addr + "/" + path
702
svnclient.exception_style = 1
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
705
svnclient.callback_get_login = get_login
722
707
except pysvn.ClientError, e:
723
708
# Error code 170000 means ENOENT in this revision.
724
709
if e[1][0][1] == 170000:
726
raise ActionError('The specified repository path does not exist')
710
raise util.IVLEError(404, 'The specified repository path does not exist')
728
712
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))
747
714
# Table of all action functions #
748
715
# Each function has the interface f(req, fields).
762
729
"svnupdate" : action_svnupdate,
763
730
"svnresolved" : action_svnresolved,
764
731
"svnrevert" : action_svnrevert,
732
"svnpublish" : action_svnpublish,
733
"svnunpublish" : action_svnunpublish,
765
734
"svncommit" : action_svncommit,
766
735
"svncheckout" : action_svncheckout,
767
736
"svnrepomkdir" : action_svnrepomkdir,
768
737
"svnrepostat" : action_svnrepostat,
769
"svncleanup" : action_svncleanup,