using System; using System.IO; using System.Runtime.InteropServices; using LibGit2Sharp.Core; namespace LibGit2Sharp { /// /// When an OdbBackend implements the WriteStream or ReadStream methods, it returns an OdbBackendStream to libgit2. /// Libgit2 then uses the OdbBackendStream to read or write from the backend in a streaming fashion. /// public abstract class OdbBackendStream { /// /// This is to quiet the MetaFixture.TypesInLibGit2SharpMustBeExtensibleInATestingContext test. /// Do not use this constructor. /// protected internal OdbBackendStream() { throw new InvalidOperationException(); } /// /// Base constructor for OdbBackendStream. Make sure that your derived class calls this base constructor. /// /// The backend to which this backend stream is attached. protected OdbBackendStream(OdbBackend backend) { this.backend = backend; } /// /// Invoked by libgit2 when this stream is no longer needed. /// protected virtual void Dispose() { if (IntPtr.Zero != nativeBackendStreamPointer) { GCHandle.FromIntPtr(Marshal.ReadIntPtr(nativeBackendStreamPointer, GitOdbBackendStream.GCHandleOffset)).Free(); Marshal.FreeHGlobal(nativeBackendStreamPointer); nativeBackendStreamPointer = IntPtr.Zero; } } /// /// If true, then it is legal to call the Read method. /// public abstract bool CanRead { get; } /// /// If true, then it is legal to call the Write and FinalizeWrite methods. /// public abstract bool CanWrite { get; } /// /// Requests that the stream write the next length bytes of the stream to the provided Stream object. /// public abstract int Read( Stream dataStream, long length); /// /// Requests that the stream write the first length bytes of the provided Stream object to the stream. /// public abstract int Write( Stream dataStream, long length); /// /// After all bytes have been written to the stream, the object ID is provided to FinalizeWrite. /// public abstract int FinalizeWrite( ObjectId id); /// /// The backend object this stream was created by. /// public virtual OdbBackend Backend { get { return this.backend; } } private readonly OdbBackend backend; private IntPtr nativeBackendStreamPointer; internal IntPtr GitOdbBackendStreamPointer { get { if (IntPtr.Zero == nativeBackendStreamPointer) { var nativeBackendStream = new GitOdbBackendStream(); nativeBackendStream.Backend = this.backend.GitOdbBackendPointer; nativeBackendStream.Free = BackendStreamEntryPoints.FreeCallback; if (CanRead) { nativeBackendStream.Read = BackendStreamEntryPoints.ReadCallback; nativeBackendStream.Mode |= GitOdbBackendStreamMode.Read; } if (CanWrite) { nativeBackendStream.Write = BackendStreamEntryPoints.WriteCallback; nativeBackendStream.FinalizeWrite = BackendStreamEntryPoints.FinalizeWriteCallback; nativeBackendStream.Mode |= GitOdbBackendStreamMode.Write; } nativeBackendStream.GCHandle = GCHandle.ToIntPtr(GCHandle.Alloc(this)); nativeBackendStreamPointer = Marshal.AllocHGlobal(Marshal.SizeOf(nativeBackendStream)); Marshal.StructureToPtr(nativeBackendStream, nativeBackendStreamPointer, false); } return nativeBackendStreamPointer; } } private static class BackendStreamEntryPoints { // Because our GitOdbBackendStream structure exists on the managed heap only for a short time (to be marshaled // to native memory with StructureToPtr), we need to bind to static delegates. If at construction time // we were to bind to the methods directly, that's the same as newing up a fresh delegate every time. // Those delegates won't be rooted in the object graph and can be collected as soon as StructureToPtr finishes. public static readonly GitOdbBackendStream.read_callback ReadCallback = Read; public static readonly GitOdbBackendStream.write_callback WriteCallback = Write; public static readonly GitOdbBackendStream.finalize_write_callback FinalizeWriteCallback = FinalizeWrite; public static readonly GitOdbBackendStream.free_callback FreeCallback = Free; private unsafe static int Read( IntPtr stream, IntPtr buffer, UIntPtr len) { OdbBackendStream odbBackendStream = GCHandle.FromIntPtr(Marshal.ReadIntPtr(stream, GitOdbBackendStream.GCHandleOffset)).Target as OdbBackendStream; if (odbBackendStream != null) { long length = OdbBackend.ConverToLong(len); using (UnmanagedMemoryStream memoryStream = new UnmanagedMemoryStream((byte*)buffer, 0, length, FileAccess.ReadWrite)) { try { return odbBackendStream.Read(memoryStream, length); } catch (Exception ex) { Proxy.giterr_set_str(GitErrorCategory.Odb, ex); } } } return (int)GitErrorCode.Error; } private static unsafe int Write( IntPtr stream, IntPtr buffer, UIntPtr len) { OdbBackendStream odbBackendStream = GCHandle.FromIntPtr(Marshal.ReadIntPtr(stream, GitOdbBackendStream.GCHandleOffset)).Target as OdbBackendStream; if (odbBackendStream != null) { long length = OdbBackend.ConverToLong(len); using (UnmanagedMemoryStream dataStream = new UnmanagedMemoryStream((byte*)buffer, length)) { try { return odbBackendStream.Write(dataStream, length); } catch (Exception ex) { Proxy.giterr_set_str(GitErrorCategory.Odb, ex); } } } return (int)GitErrorCode.Error; } private static int FinalizeWrite( IntPtr stream, ref GitOid oid) { OdbBackendStream odbBackendStream = GCHandle.FromIntPtr(Marshal.ReadIntPtr(stream, GitOdbBackendStream.GCHandleOffset)).Target as OdbBackendStream; if (odbBackendStream != null) { try { return odbBackendStream.FinalizeWrite(new ObjectId(oid)); } catch (Exception ex) { Proxy.giterr_set_str(GitErrorCategory.Odb, ex); } } return (int)GitErrorCode.Error; } private static void Free( IntPtr stream) { OdbBackendStream odbBackendStream = GCHandle.FromIntPtr(Marshal.ReadIntPtr(stream, GitOdbBackendStream.GCHandleOffset)).Target as OdbBackendStream; if (odbBackendStream != null) { try { odbBackendStream.Dispose(); } catch (Exception ex) { Proxy.giterr_set_str(GitErrorCategory.Odb, ex); } } } } } }