using System; using System.IO; using System.Runtime.InteropServices; using LibGit2Sharp.Core; namespace LibGit2Sharp { /// /// Base class for all custom managed backends for the libgit2 object database (ODB). /// public abstract class OdbBackend { /// /// Invoked by libgit2 when this backend is no longer needed. /// protected virtual void Dispose() { if (IntPtr.Zero != nativeBackendPointer) { GCHandle.FromIntPtr(Marshal.ReadIntPtr(nativeBackendPointer, GitOdbBackend.GCHandleOffset)).Free(); Marshal.FreeHGlobal(nativeBackendPointer); nativeBackendPointer = IntPtr.Zero; } } /// /// In your subclass, override this member to provide the list of actions your backend supports. /// protected abstract OdbBackendOperations SupportedOperations { get; } /// /// Call this method from your implementations of Read and ReadPrefix to allocate a buffer in /// which to return the object's data. /// /// Number of bytes to allocate /// An Stream for you to write to and then return. Do not dispose this object before returning it. protected unsafe Stream Allocate(long bytes) { if (bytes < 0 || (UIntPtr.Size == sizeof(int) && bytes > int.MaxValue)) { throw new ArgumentOutOfRangeException("bytes"); } IntPtr buffer = Proxy.git_odb_backend_malloc(this.GitOdbBackendPointer, new UIntPtr((ulong)bytes)); return new UnmanagedMemoryStream((byte*)buffer, 0, bytes, FileAccess.ReadWrite); } /// /// Requests that this backend read an object. /// public abstract int Read(byte[] oid, out Stream data, out ObjectType objectType); /// /// Requests that this backend read an object. The object ID may not be complete (may be a prefix). /// public abstract int ReadPrefix(byte[] shortOid, out byte[] oid, out Stream data, out ObjectType objectType); /// /// Requests that this backend read an object's header (length and object type) but not its contents. /// public abstract int ReadHeader(byte[] oid, out int length, out ObjectType objectType); /// /// Requests that this backend write an object to the backing store. The backend may need to compute the object ID /// and return it to the caller. /// public abstract int Write(byte[] oid, Stream dataStream, long length, ObjectType objectType, out byte[] finalOid); /// /// Requests that this backend read an object. Returns a stream so that the caller can read the data in chunks. /// public abstract int ReadStream(byte[] oid, out OdbBackendStream stream); /// /// Requests that this backend write an object to the backing store. Returns a stream so that the caller can write /// the data in chunks. /// public abstract int WriteStream(long length, ObjectType objectType, out OdbBackendStream stream); /// /// Requests that this backend check if an object ID exists. /// public abstract bool Exists(byte[] oid); /// /// Requests that this backend enumerate all items in the backing store. /// public abstract int ForEach(ForEachCallback callback); /// /// The signature of the callback method provided to the Foreach method. /// /// The object ID of the object in the backing store. /// A non-negative result indicates the enumeration should continue. Otherwise, the enumeration should stop. public delegate int ForEachCallback(byte[] oid); private IntPtr nativeBackendPointer; internal IntPtr GitOdbBackendPointer { get { if (IntPtr.Zero == nativeBackendPointer) { var nativeBackend = new GitOdbBackend(); nativeBackend.Version = 1; // The "free" entry point is always provided. nativeBackend.Free = BackendEntryPoints.FreeCallback; var supportedOperations = this.SupportedOperations; if ((supportedOperations & OdbBackendOperations.Read) != 0) { nativeBackend.Read = BackendEntryPoints.ReadCallback; } if ((supportedOperations & OdbBackendOperations.ReadPrefix) != 0) { nativeBackend.ReadPrefix = BackendEntryPoints.ReadPrefixCallback; } if ((supportedOperations & OdbBackendOperations.ReadHeader) != 0) { nativeBackend.ReadHeader = BackendEntryPoints.ReadHeaderCallback; } if ((supportedOperations & OdbBackendOperations.ReadStream) != 0) { nativeBackend.ReadStream = BackendEntryPoints.ReadStreamCallback; } if ((supportedOperations & OdbBackendOperations.Write) != 0) { nativeBackend.Write = BackendEntryPoints.WriteCallback; } if ((supportedOperations & OdbBackendOperations.WriteStream) != 0) { nativeBackend.WriteStream = BackendEntryPoints.WriteStreamCallback; } if ((supportedOperations & OdbBackendOperations.Exists) != 0) { nativeBackend.Exists = BackendEntryPoints.ExistsCallback; } if ((supportedOperations & OdbBackendOperations.ForEach) != 0) { nativeBackend.Foreach = BackendEntryPoints.ForEachCallback; } nativeBackend.GCHandle = GCHandle.ToIntPtr(GCHandle.Alloc(this)); nativeBackendPointer = Marshal.AllocHGlobal(Marshal.SizeOf(nativeBackend)); Marshal.StructureToPtr(nativeBackend, nativeBackendPointer, false); } return nativeBackendPointer; } } private static class BackendEntryPoints { // Because our GitOdbBackend 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 GitOdbBackend.read_callback ReadCallback = Read; public static readonly GitOdbBackend.read_prefix_callback ReadPrefixCallback = ReadPrefix; public static readonly GitOdbBackend.read_header_callback ReadHeaderCallback = ReadHeader; public static readonly GitOdbBackend.readstream_callback ReadStreamCallback = ReadStream; public static readonly GitOdbBackend.write_callback WriteCallback = Write; public static readonly GitOdbBackend.writestream_callback WriteStreamCallback = WriteStream; public static readonly GitOdbBackend.exists_callback ExistsCallback = Exists; public static readonly GitOdbBackend.foreach_callback ForEachCallback = Foreach; public static readonly GitOdbBackend.free_callback FreeCallback = Free; private unsafe static int Read( out IntPtr buffer_p, out UIntPtr len_p, out GitObjectType type_p, IntPtr backend, ref GitOid oid) { buffer_p = IntPtr.Zero; len_p = UIntPtr.Zero; type_p = GitObjectType.Bad; OdbBackend odbBackend = GCHandle.FromIntPtr(Marshal.ReadIntPtr(backend, GitOdbBackend.GCHandleOffset)).Target as OdbBackend; if (odbBackend != null) { Stream dataStream = null; ObjectType objectType; try { int toReturn = odbBackend.Read(oid.Id, out dataStream, out objectType); if (0 == toReturn) { // Caller is expected to give us back a stream created with the Allocate() method. UnmanagedMemoryStream memoryStream = dataStream as UnmanagedMemoryStream; if (null == memoryStream) { return (int)GitErrorCode.Error; } len_p = new UIntPtr((ulong)memoryStream.Capacity); type_p = objectType.ToGitObjectType(); memoryStream.Seek(0, SeekOrigin.Begin); buffer_p = new IntPtr(memoryStream.PositionPointer); } return toReturn; } catch (Exception ex) { Proxy.giterr_set_str(GitErrorCategory.Odb, ex); } finally { if (null != dataStream) { dataStream.Dispose(); } } } return (int)GitErrorCode.Error; } private unsafe static int ReadPrefix( out GitOid out_oid, out IntPtr buffer_p, out UIntPtr len_p, out GitObjectType type_p, IntPtr backend, ref GitOid short_oid, UIntPtr len) { out_oid = default(GitOid); buffer_p = IntPtr.Zero; len_p = UIntPtr.Zero; type_p = GitObjectType.Bad; OdbBackend odbBackend = GCHandle.FromIntPtr(Marshal.ReadIntPtr(backend, GitOdbBackend.GCHandleOffset)).Target as OdbBackend; if (odbBackend != null) { byte[] oid; Stream dataStream = null; ObjectType objectType; try { // The length of short_oid is described in characters (40 per full ID) vs. bytes (20) // which is what we care about. byte[] shortOidArray = new byte[(long)len >> 1]; Array.Copy(short_oid.Id, shortOidArray, shortOidArray.Length); int toReturn = odbBackend.ReadPrefix(shortOidArray, out oid, out dataStream, out objectType); if (0 == toReturn) { // Caller is expected to give us back a stream created with the Allocate() method. UnmanagedMemoryStream memoryStream = dataStream as UnmanagedMemoryStream; if (null == memoryStream) { return (int)GitErrorCode.Error; } out_oid.Id = oid; len_p = new UIntPtr((ulong)memoryStream.Capacity); type_p = objectType.ToGitObjectType(); memoryStream.Seek(0, SeekOrigin.Begin); buffer_p = new IntPtr(memoryStream.PositionPointer); } return toReturn; } catch (Exception ex) { Proxy.giterr_set_str(GitErrorCategory.Odb, ex); } finally { if (null != dataStream) { dataStream.Dispose(); } } } return (int)GitErrorCode.Error; } private static int ReadHeader( out UIntPtr len_p, out GitObjectType type_p, IntPtr backend, ref GitOid oid) { len_p = UIntPtr.Zero; type_p = GitObjectType.Bad; OdbBackend odbBackend = GCHandle.FromIntPtr(Marshal.ReadIntPtr(backend, GitOdbBackend.GCHandleOffset)).Target as OdbBackend; if (odbBackend != null) { int length; ObjectType objectType; try { int toReturn = odbBackend.ReadHeader(oid.Id, out length, out objectType); if (0 == toReturn) { len_p = new UIntPtr((uint)length); type_p = objectType.ToGitObjectType(); } return toReturn; } catch (Exception ex) { Proxy.giterr_set_str(GitErrorCategory.Odb, ex); } } return (int)GitErrorCode.Error; } private static unsafe int Write( ref GitOid oid, IntPtr backend, IntPtr data, UIntPtr len, GitObjectType type) { OdbBackend odbBackend = GCHandle.FromIntPtr(Marshal.ReadIntPtr(backend, GitOdbBackend.GCHandleOffset)).Target as OdbBackend; ObjectType objectType = type.ToObjectType(); if (odbBackend != null && len.ToUInt64() < long.MaxValue) { try { using (UnmanagedMemoryStream stream = new UnmanagedMemoryStream((byte*)data, (long)len.ToUInt64())) { byte[] finalOid; int toReturn = odbBackend.Write(oid.Id, stream, (long)len.ToUInt64(), objectType, out finalOid); if (0 == toReturn) { oid.Id = finalOid; } return toReturn; } } catch (Exception ex) { Proxy.giterr_set_str(GitErrorCategory.Odb, ex); } } return (int)GitErrorCode.Error; } private static int WriteStream( out IntPtr stream_out, IntPtr backend, UIntPtr length, GitObjectType type) { stream_out = IntPtr.Zero; OdbBackend odbBackend = GCHandle.FromIntPtr(Marshal.ReadIntPtr(backend, GitOdbBackend.GCHandleOffset)).Target as OdbBackend; ObjectType objectType = type.ToObjectType(); if (odbBackend != null && length.ToUInt64() < long.MaxValue) { OdbBackendStream stream; try { int toReturn = odbBackend.WriteStream((long)length.ToUInt64(), objectType, out stream); if (0 == toReturn) { stream_out = stream.GitOdbBackendStreamPointer; } return toReturn; } catch (Exception ex) { Proxy.giterr_set_str(GitErrorCategory.Odb, ex); } } return (int)GitErrorCode.Error; } private static int ReadStream( out IntPtr stream_out, IntPtr backend, ref GitOid oid) { stream_out = IntPtr.Zero; OdbBackend odbBackend = GCHandle.FromIntPtr(Marshal.ReadIntPtr(backend, GitOdbBackend.GCHandleOffset)).Target as OdbBackend; if (odbBackend != null) { OdbBackendStream stream; try { int toReturn = odbBackend.ReadStream(oid.Id, out stream); if (0 == toReturn) { stream_out = stream.GitOdbBackendStreamPointer; } return toReturn; } catch (Exception ex) { Proxy.giterr_set_str(GitErrorCategory.Odb, ex); } } return (int)GitErrorCode.Error; } private static bool Exists( IntPtr backend, ref GitOid oid) { OdbBackend odbBackend = GCHandle.FromIntPtr(Marshal.ReadIntPtr(backend, GitOdbBackend.GCHandleOffset)).Target as OdbBackend; if (odbBackend != null) { try { return odbBackend.Exists(oid.Id); } catch (Exception ex) { Proxy.giterr_set_str(GitErrorCategory.Odb, ex); } } return false; } private static int Foreach( IntPtr backend, GitOdbBackend.foreach_callback_callback cb, IntPtr data) { OdbBackend odbBackend = GCHandle.FromIntPtr(Marshal.ReadIntPtr(backend, GitOdbBackend.GCHandleOffset)).Target as OdbBackend; if (odbBackend != null) { try { return odbBackend.ForEach(new ForeachState(cb, data).ManagedCallback); } catch (Exception ex) { Proxy.giterr_set_str(GitErrorCategory.Odb, ex); } } return (int)GitErrorCode.Error; } private static void Free( IntPtr backend) { GCHandle gcHandle = GCHandle.FromIntPtr(Marshal.ReadIntPtr(backend, GitOdbBackend.GCHandleOffset)); OdbBackend odbBackend = gcHandle.Target as OdbBackend; if (odbBackend != null) { try { odbBackend.Dispose(); } catch (Exception ex) { Proxy.giterr_set_str(GitErrorCategory.Odb, ex); } } } private class ForeachState { public ForeachState(GitOdbBackend.foreach_callback_callback cb, IntPtr data) { this.cb = cb; this.data = data; this.ManagedCallback = CallbackMethod; } private int CallbackMethod(byte[] oid) { GitOid gitOid = new GitOid(); gitOid.Id = oid; return cb(ref gitOid, data); } public readonly ForEachCallback ManagedCallback; private readonly GitOdbBackend.foreach_callback_callback cb; private readonly IntPtr data; } } /// /// Flags used by subclasses of OdbBackend to indicate which operations they support. /// [Flags] protected enum OdbBackendOperations { /// /// This OdbBackend declares that it supports the Read method. /// Read = 1, /// /// This OdbBackend declares that it supports the ReadPrefix method. /// ReadPrefix = 2, /// /// This OdbBackend declares that it supports the ReadHeader method. /// ReadHeader = 4, /// /// This OdbBackend declares that it supports the Write method. /// Write = 8, /// /// This OdbBackend declares that it supports the ReadStream method. /// ReadStream = 16, /// /// This OdbBackend declares that it supports the WriteStream method. /// WriteStream = 32, /// /// This OdbBackend declares that it supports the Exists method. /// Exists = 64, /// /// This OdbBackend declares that it supports the Foreach method. /// ForEach = 128, } } }