MODULE SVNWebDAV;
IMPORT
WebHTTP, Files, Strings, Streams, Dates,
XML, XMLObjects,KernelLog,
SVNAdmin, SVNUtil, SVNOutput,
OdSvn, OdXml;
PROCEDURE Checkout* (svn : OdSvn.OdSvn; CONST pathName : ARRAY OF CHAR; CONST workName : ARRAY OF CHAR; VAR res : LONGINT );
VAR
name, err, vcc, repoUUID, bln, bc, version, workUrl : ARRAY 256 OF CHAR;
props: WebHTTP.AdditionalField;
ver, pos : LONGINT;
BEGIN
svn.checkout := TRUE;
res := SVNOutput.ResOK;
Files.SplitPath ( pathName, workUrl, name );
svn.repositoryPathLength := 999;
IF Files.Old ( workName ) = NIL THEN
COPY ( pathName, workUrl );
svn.FileUpdate ( FALSE );
ELSE
svn.FileUpdate ( TRUE );
END;
svn.Propfind ( workUrl, "D:version-controlled-configuration.D:resourcetype.D2:baseline-relative-path.D2:repository-uuid", props, err );
IF (svn.pfStatus >= 400) OR (svn.pfStatus = 0) THEN
PrintError ( svn, res );
RETURN;
END;
IF ~WebHTTP.GetAdditionalFieldValue(props, "DAV:version-controlled-configuration", vcc) THEN END;
IF ~WebHTTP.GetAdditionalFieldValue(props, "http://subversion.tigris.org/xmlns/dav/repository-uuid", repoUUID) THEN END;
svn.Propfind ( vcc, "D:checked-in", props, err );
IF ~WebHTTP.GetAdditionalFieldValue ( props, "DAV:checked-in", bln ) THEN END;
svn.Propfind ( bln, "D:baseline-collection.D:version-name", props, err );
IF ~WebHTTP.GetAdditionalFieldValue ( props, "DAV:baseline-collection", bc ) THEN END;
IF ~WebHTTP.GetAdditionalFieldValue ( props, "DAV:version-name", version ) THEN END;
IF err # "" THEN
svn.context.out.String ( err ); svn.context.out.Ln;
RETURN;
END;
Strings.StrToIntPos(version, ver, pos);
svn.UpdateReport ( pathName, vcc, workUrl, -1, ver, workName, res );
svn.checkout := FALSE;
END Checkout;
PROCEDURE Update* (svn : OdSvn.OdSvn; CONST pathName : ARRAY OF CHAR; pathNameVersion : LONGINT; CONST workName : ARRAY OF CHAR; VAR res : LONGINT );
VAR
name, err, vcc, repoUUID, bln, bc, version, workUrl : ARRAY 256 OF CHAR;
props: WebHTTP.AdditionalField;
ver, pos : LONGINT;
BEGIN
res := SVNOutput.ResOK;
Files.SplitPath ( pathName, workUrl, name );
IF Files.Old ( workName ) = NIL THEN
COPY ( pathName, workUrl );
svn.FileUpdate ( FALSE );
ELSE
svn.FileUpdate ( TRUE );
END;
svn.traverseDummy := TRUE;
SVNAdmin.Traverse ( workName, UpdateHandler, svn, FALSE, res );
svn.Propfind ( workUrl, "D:version-controlled-configuration.D:resourcetype.D2:baseline-relative-path.D2:repository-uuid", props, err );
IF (svn.pfStatus >= 400) OR (svn.pfStatus = 0) THEN
PrintError ( svn, res );
RETURN;
END;
IF ~WebHTTP.GetAdditionalFieldValue(props, "DAV:version-controlled-configuration", vcc) THEN END;
IF ~WebHTTP.GetAdditionalFieldValue(props, "http://subversion.tigris.org/xmlns/dav/repository-uuid", repoUUID) THEN END;
svn.Propfind ( vcc, "D:checked-in", props, err );
IF ~WebHTTP.GetAdditionalFieldValue ( props, "DAV:checked-in", bln ) THEN END;
svn.Propfind ( bln, "D:baseline-collection.D:version-name", props, err );
IF ~WebHTTP.GetAdditionalFieldValue ( props, "DAV:baseline-collection", bc ) THEN END;
IF ~WebHTTP.GetAdditionalFieldValue ( props, "DAV:version-name", version ) THEN END;
IF err # "" THEN
svn.context.out.String ( err ); svn.context.out.Ln;
RETURN;
END;
Strings.StrToIntPos(version, ver, pos);
svn.UpdateReport ( pathName, vcc, workUrl, pathNameVersion, ver, workName, res );
END Update;
PROCEDURE Commit* ( svn : OdSvn.OdSvn; CONST pathName, workName, message : ARRAY OF CHAR; VAR res : LONGINT );
VAR
act: OdSvn.Activity;
uuid : Strings.String;
name, err, vcc, bln, workUrl, wbl : ARRAY 256 OF CHAR;
props, patch : WebHTTP.AdditionalField;
resHeader: WebHTTP.ResponseHeader;
BEGIN
uuid := SVNUtil.GetUUID();
NEW ( act, svn.client, uuid^ );
ASSERT ( act # NIL );
act.make;
IF act.getUrl() = NIL THEN
svn.pfStatus := 0;
PrintError ( svn, res );
RETURN;
END;
Files.SplitPath ( pathName, workUrl, name );
IF Files.Old ( workName ) = NIL THEN
COPY ( pathName, workUrl );
END;
svn.Propfind ( workUrl, "D:version-controlled-configuration", props, err );
IF ~WebHTTP.GetAdditionalFieldValue(props, "DAV:version-controlled-configuration", vcc) THEN END;
IF (svn.pfStatus >= 400) OR (svn.pfStatus = 0) THEN
PrintError ( svn, res );
RETURN;
END;
svn.Propfind ( vcc, "D:checked-in", props, err );
IF ~WebHTTP.GetAdditionalFieldValue ( props, "DAV:checked-in", bln ) THEN END;
svn.Checkout ( bln, resHeader, err );
COPY ( resHeader.location, wbl );
WebHTTP.SetAdditionalFieldValue ( patch, "log xmlns=http://subversion.tigris.org/xmlns/svn/", message );
svn.Proppatch ( wbl, patch, err );
svn.Propfind ( workUrl, "D:checked-in", props, err );
IF ~WebHTTP.GetAdditionalFieldValue(props, "DAV:checked-in", svn.ver) THEN END;
svn.Checkout ( svn.ver, resHeader, err );
COPY ( resHeader.location, svn.wrk );
svn.removeDir := FALSE;
svn.countChanges := 0;
SVNAdmin.Traverse ( workName, CommitHandler, svn, TRUE, res );
IF svn.countChanges > 0 THEN
svn.Merge ( workUrl, act.getUrl(), resHeader, err );
IF resHeader.statuscode = 200 THEN
ParseMergeContent ( svn, workUrl, workName );
END;
END;
act.delete;
END Commit;
PROCEDURE ParseMergeContent ( svn : OdSvn.OdSvn; CONST baseUrl, basePath : ARRAY OF CHAR );
VAR
root, e, e2 : XML.Element;
enum : XMLObjects.Enumerator;
str, md5 : Strings.String;
p : ANY;
xml : OdXml.OdXml;
vcc, vurl, ver, tmp, tmp2, path, name, date : ARRAY 256 OF CHAR;
creationdate, creator, status : ARRAY 33 OF CHAR;
version : ARRAY 10 OF CHAR;
len, res, ft,fd : LONGINT;
adminDir : SVNAdmin.Entry;
f : Files.File;
BEGIN
NEW ( xml );
NEW ( adminDir, NIL );
root := svn.resultDoc.GetRoot();
str := root.GetName();
IF str^ # "D:merge-response" THEN RETURN END;
enum := root.GetContents();
IF ~enum.HasMoreElements() THEN RETURN END;
p := enum.GetNext();
e := p ( XML.Element );
str := e.GetName();
IF str^ # "D:updated-set" THEN RETURN END;
enum := e.GetContents();
IF ~enum.HasMoreElements() THEN RETURN END;
p := enum.GetNext();
e := p ( XML.Element );
str := e.GetName();
IF str^ # "D:response" THEN RETURN END;
e2 := xml.SplitElement ( e, "DAV:href" );
ASSERT ( e2 # NIL );
OdXml.GetCharData ( e2, vcc );
e2 := xml.SplitElement ( e, "DAV:propstat.DAV:prop.DAV:creationdate" ); ASSERT ( e2 # NIL );
OdXml.GetCharData ( e2, creationdate );
e2 := xml.SplitElement ( e, "DAV:propstat.DAV:prop.DAV:version-name" ); ASSERT ( e2 # NIL );
OdXml.GetCharData ( e2, version );
e2 := xml.SplitElement ( e, "DAV:propstat.DAV:prop.DAV:creator-displayname" ); ASSERT ( e2 # NIL );
OdXml.GetCharData ( e2, creator );
len := Strings.Length ( baseUrl );
ASSERT ( ~Strings.EndsWith ( "/", basePath ) );
WHILE enum.HasMoreElements() DO
p := enum.GetNext();
IF p IS XML.Element THEN
e := p ( XML.Element );
str := e.GetName();
e2 := xml.SplitElement ( e, "DAV:href" ); ASSERT ( e2 # NIL );
OdXml.GetCharData ( e2, vurl );
e2 := xml.SplitElement ( e, "DAV:propstat.DAV:prop.DAV:checked-in.DAV:href" ); ASSERT ( e2 # NIL );
OdXml.GetCharData ( e2, ver );
e2 := xml.SplitElement ( e, "DAV:propstat.DAV:status" ); ASSERT ( e2 # NIL );
OdXml.GetCharData ( e2, status );
IF Strings.Match ( "HTTP/1.? 200 OK", status ) THEN
str := Strings.Substring2 ( len, vurl );
SVNUtil.UrlDecode ( str^, tmp2 );
Strings.Concat ( basePath, tmp2, tmp );
adminDir.SetPath ( tmp, res ); ASSERT ( res = SVNOutput.ResOK );
adminDir.CreateTempfile;
f := Files.Old ( tmp );
IF f # NIL THEN
Files.SplitPath ( tmp, path, name );
IF adminDir.IsItemVersioned ( name ) THEN
adminDir.ReadWriteLines ( 1 );
adminDir.ReadWriteString ( version );
adminDir.ReadWriteLines ( 2 );
adminDir.ReadWriteString ( "" );
adminDir.ReadWriteString ( creationdate );
md5 := SVNUtil.GetChecksum ( tmp );
adminDir.ReadWriteString ( md5^ );
f.GetDate ( ft, fd );
Strings.FormatDateTime ( SVNOutput.DateFormat, Dates.OberonToDateTime ( fd, ft ), date );
adminDir.ReadWriteString ( date );
adminDir.ReadWriteString ( version );
adminDir.ReadWriteString ( creator );
adminDir.ReadWriteRest;
adminDir.WriteUpdate;
SVNAdmin.CopyToBaseFile ( tmp );
SVNAdmin.WriteWCPROPS ( path, name, ver );
ELSE
svn.context.out.String ( "ERROR: received merge request, but item is not versioned" );
svn.context.out.Ln;
END;
ELSE
adminDir.ReadWriteLines ( 3 );
adminDir.ReadWriteString ( version );
adminDir.ReadWriteLines ( 2 );
adminDir.ReadWriteString ( "" );
adminDir.ReadWriteLines ( 2 );
adminDir.ReadWriteString ( creationdate );
adminDir.ReadWriteString ( version );
adminDir.ReadWriteString ( creator );
adminDir.ReadWriteToEOE;
WHILE ~adminDir.IsEOF () DO
adminDir.ReadWriteLines ( 1 );
adminDir.ReadWriteLine ( tmp2 );
IF tmp2 = "dir" THEN
adminDir.ReadWriteEOE;
ELSE
adminDir.ReadWriteToEOE;
END;
END;
adminDir.WriteUpdate;
SVNAdmin.WriteWCPROPS ( tmp, "", ver );
END;
END;
END;
END;
END ParseMergeContent;
PROCEDURE UpdateHandler* ( CONST path : ARRAY OF CHAR; entry : SVNAdmin.EntryEntity; data : ANY ) : BOOLEAN;
VAR
svn : OdSvn.OdSvn;
BEGIN
svn := data ( OdSvn.OdSvn );
IF svn.traverseDummy THEN
svn.traverseDummy := FALSE;
svn.repositoryPathLength := Strings.Length ( entry.RepositoryRoot ) - Strings.IndexOfByte ( '/', 7, entry.RepositoryRoot );
END;
RETURN TRUE;
END UpdateHandler;
PROCEDURE CommitHandler* ( CONST path : ARRAY OF CHAR; entry : SVNAdmin.EntryEntity; data : ANY ) : BOOLEAN;
VAR
str : Strings.String;
err, wrk, tmp2 : ARRAY 256 OF CHAR;
props: WebHTTP.AdditionalField;
svn : OdSvn.OdSvn;
res : LONGINT;
resHeader: WebHTTP.ResponseHeader;
m : SVNOutput.Message;
BEGIN
svn := data ( OdSvn.OdSvn );
NEW ( str, 256 );
str := Strings.Substring2 ( Strings.Length ( entry.RepositoryRoot ), entry.Url );
IF str^[0] = Files.PathDelimiter THEN str := Strings.Substring2 ( 1, str^ ) END;
IF ~svn.removeDir THEN
svn.removeDir := TRUE;
Strings.Truncate ( svn.wrk, Strings.Length(svn.wrk) - Strings.Length(str^) );
END;
Strings.Concat ( svn.wrk, str^, tmp2 );
SVNUtil.UrlEncode ( tmp2, wrk );
IF entry.NodeKind = "file" THEN
Files.JoinPath ( path, entry.Name, tmp2 );
IF entry.Schedule = "add" THEN
svn.Propfind ( wrk, "D:version-controlled-configuration.D2:repository-uuid", props, err );
IF svn.pfStatus = 404 THEN
svn.context.out.String ( "Adding " ); svn.context.out.String ( tmp2 ); svn.context.out.Ln;
Put ( wrk, tmp2, svn, res );
ExpectedResult ( 201, svn, wrk, tmp2, "add file" );
ELSE
svn.context.out.String ( " ERROR: " ); svn.context.out.String ( wrk ); svn.context.out.String ( " is already on the server!" ); svn.context.out.Ln;
svn.countChanges := 0;
RETURN FALSE;
END;
ELSIF entry.Schedule = "delete" THEN
ELSE
IF ~SVNUtil.CheckChecksum ( tmp2, entry.Checksum ) THEN
svn.context.out.String ( "Sending " );
svn.context.out.String ( tmp2 );
svn.context.out.Ln;
svn.Checkout ( entry.VersionUrl, resHeader, err );
IF resHeader.statuscode >= 400 THEN
NEW ( m, svn.context );
IF resHeader.statuscode = 409 THEN
m.Print ( SVNOutput.ResCOMMITOUTOFDATE, tmp2 );
ELSE
svn.context.out.String ( "HTTP error! Statuscode: " ); svn.context.out.Int ( resHeader.statuscode, 0 );
svn.context.out.Ln;
m.Print ( SVNOutput.ResCOMMITUNSPECIFIED, tmp2 );
END;
svn.countChanges := 0;
RETURN FALSE;
ELSE
Put ( wrk, tmp2, svn, res );
ExpectedResult ( 204, svn, wrk, tmp2, "add file" );
END;
END;
END;
ELSE
IF entry.Schedule = "add" THEN
svn.context.out.String ( "Adding " ); svn.context.out.String ( path ); svn.context.out.Ln;
Mkcol ( wrk, svn, res );
ExpectedResult ( 201, svn, wrk, tmp2, "add directory" );
ELSIF entry.Schedule = "delete" THEN
ELSE
END;
END;
RETURN TRUE;
END CommitHandler;
PROCEDURE ExpectedResult ( status : LONGINT; svn : OdSvn.OdSvn; CONST wrk, lcl, message : ARRAY OF CHAR );
BEGIN
IF svn.pfStatus # status THEN
svn.context.out.String ( "ERROR: failed to " );
svn.context.out.String ( message );
svn.context.out.String ( ": " );
svn.context.out.String ( lcl );
svn.context.out.Ln;
svn.context.out.String ( "url: " );
svn.context.out.String ( wrk );
svn.context.out.Ln;
ELSE
INC ( svn.countChanges );
END;
END ExpectedResult;
PROCEDURE Mkcol* ( CONST url : ARRAY OF CHAR; svn : OdSvn.OdSvn; VAR res : LONGINT );
VAR
resHeader: WebHTTP.ResponseHeader;
out : Streams.Reader;
BEGIN
svn.client.Mkcol ( url, resHeader, out, res );
svn.pfStatus := resHeader.statuscode;
END Mkcol;
PROCEDURE Delete* ( CONST url : ARRAY OF CHAR; svn : OdSvn.OdSvn; VAR res : LONGINT );
VAR
resHeader: WebHTTP.ResponseHeader;
out : Streams.Reader;
BEGIN
svn.client.Delete ( url, resHeader, out, res );
svn.pfStatus := resHeader.statuscode;
END Delete;
PROCEDURE Put* ( CONST workUrl, workName: ARRAY OF CHAR; svn : OdSvn.OdSvn; VAR res : LONGINT );
VAR
f : Files.File;
resHeader: WebHTTP.ResponseHeader;
reqHeader: WebHTTP.RequestHeader;
in : Files.Reader;
out : Streams.Reader;
lenStr: ARRAY 10 OF CHAR;
m : SVNOutput.Message;
BEGIN
f := Files.Old(workName);
IF f # NIL THEN
Files.OpenReader ( in, f, 0 );
WebHTTP.SetAdditionalFieldValue ( reqHeader.additionalFields, "Content-Type", "application/octet-stream" );
Strings.IntToStr ( f.Length(), lenStr );
WebHTTP.SetAdditionalFieldValue ( reqHeader.additionalFields, "Content-Length", lenStr );
svn.client.Put ( workUrl, reqHeader, resHeader, out, in, res );
svn.pfStatus := resHeader.statuscode;
ELSE
svn.context.out.String ( " ERROR: PUT " );
NEW ( m, svn.context );
m.Print ( SVNOutput.ResFILENOTFOUND, workName );
END;
END Put;
PROCEDURE PrintError ( svn : OdSvn.OdSvn; VAR res : LONGINT );
BEGIN
svn.context.out.String ( "Server response: " ); svn.context.out.Int ( svn.pfStatus, 0 ); svn.context.out.Ln;
IF svn.pfStatus = 401 THEN
res := SVNOutput.ResNOTAUTHORIZED;
ELSE
res := SVNOutput.ResUNEXPECTEDSERVERRESPONSE;
END;
END PrintError;
END SVNWebDAV.