123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505 |
- MODULE SVNWebDAV; (** AUTHOR "rstoll"; *)
- 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 : WORD );
- 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; (* we don't want a update-target in the update request *)
- 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;
- (* SVN update using .svn directories *)
- PROCEDURE Update* (svn : OdSvn.OdSvn; CONST pathName : ARRAY OF CHAR; pathNameVersion : LONGINT; CONST workName : ARRAY OF CHAR; VAR res : WORD );
- 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; (* first call *)
- SVNAdmin.Traverse ( workName, UpdateHandler, svn, FALSE, res ); (* don't read the version url from wcprops *)
- 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 : WORD );
- 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 );
- (* don't merge if there are no modified files *)
- 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, ft,fd : LONGINT; res: WORD;
- 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:resourcetype.DAV:collection" );
- IF e2 = NIL THEN
- (*KernelLog.String ( "NIL collection" );*)
- ELSE
- (*KernelLog.String ( "NOT NIL collection" );*)
- END;*)
- 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 ( "" ); (* remove scheduled stuff *)
- 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 ( "" ); (* remove scheduled stuff *)
- adminDir.ReadWriteLines ( 2 );
- adminDir.ReadWriteString ( creationdate ); (* correct? *)
- adminDir.ReadWriteString ( version );
- adminDir.ReadWriteString ( creator );
- (* remove schedule entries *)
- 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
- (* TODO:
- - search for files/directories which are on a separate revision
- - search for missing resources..and restore them:
- - dirs: neuer request!
- - files: restore von base copy
- *)
- (* KernelLog.String ( "path: " );
- KernelLog.String ( path );
- KernelLog.String ( "//" );
- KernelLog.String ( entry.Name );
- KernelLog.Ln;*)
- 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 : WORD;
- 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 (* DONE *)
- svn.Propfind ( wrk, "D:version-controlled-configuration.D2:repository-uuid", props, err );
- IF svn.pfStatus = 404 THEN (* 404 = not found *)
- 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" ); (* 201 = created *)
- 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; (* abort commit *)
- RETURN FALSE;
- END;
- ELSIF entry.Schedule = "delete" THEN
- (* TODO: remove entry in entries file and all-wcprops file: there is no report packet !!! *)
- (*IF ~entry.GlobalRemoval THEN (* file will be deleted anyway if the top directory is removed *)
- svn.context.out.String ( "Deleting " ); svn.context.out.String ( tmp2 ); svn.context.out.Ln;
- Delete ( wrk, svn, res );
- ExpectedResult ( 204, svn, wrk, tmp2, "delete file" ); (* 204 = no content *)
- END;*)
- ELSE (* DONE *)
- 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; (* abort commit *)
- RETURN FALSE;
- ELSE
- Put ( wrk, tmp2, svn, res );
- ExpectedResult ( 204, svn, wrk, tmp2, "add file" ); (* 204 = no content *)
- END;
- END;
- END;
- ELSE
- IF entry.Schedule = "add" THEN (* DONE *)
- 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
- (* TODO: remove entry in entries file *)
- (*svn.context.out.String ( "Deleting " ); svn.context.out.String ( tmp2 ); svn.context.out.Ln;
- Delete ( wrk, svn, res );
- ExpectedResult ( 204, svn, wrk, tmp2, "delete file" ); (* 204 = no content *)
- *)
- ELSE
- (* do nothing *)
- 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 : WORD );
- 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 : WORD );
- VAR
- resHeader: WebHTTP.ResponseHeader;
- out : Streams.Reader;
- BEGIN
- svn.client.Delete ( url, resHeader, out, res );
- svn.pfStatus := resHeader.statuscode;
- END Delete;
- (* sends 'workName' to the svn.wrk address *)
- PROCEDURE Put* ( CONST workUrl, workName: ARRAY OF CHAR; svn : OdSvn.OdSvn; VAR res : WORD );
- 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 : WORD );
- 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.
|