using System;
using System.Globalization;
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).
///
/// If the derived backend implements , the
/// method will be honored and invoked upon the disposal of the repository.
///
///
public abstract class OdbBackend
{
///
/// Invoked by libgit2 when this backend is no longer needed.
///
internal void Free()
{
if (nativeBackendPointer == IntPtr.Zero)
{
return;
}
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.
///
/// The bytes to be copied to the stream.
///
/// A Stream already filled with the content of provided the byte array.
/// Do not dispose this object before returning it.
///
protected UnmanagedMemoryStream AllocateAndBuildFrom(byte[] bytes)
{
var stream = Allocate(bytes.Length);
stream.Write(bytes, 0, bytes.Length);
return stream;
}
///
/// 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
/// A Stream for you to write to and then return. Do not dispose this object before returning it.
protected unsafe UnmanagedMemoryStream Allocate(long size)
{
if (size < 0 ||
(UIntPtr.Size == sizeof(int) && size > int.MaxValue))
{
throw new ArgumentOutOfRangeException("size");
}
IntPtr buffer = Proxy.git_odb_backend_malloc(this.GitOdbBackendPointer, new UIntPtr((ulong)size));
return new UnmanagedMemoryStream((byte*)buffer, 0, size, FileAccess.ReadWrite);
}
///
/// Requests that this backend read an object.
///
public abstract int Read(
ObjectId id,
out UnmanagedMemoryStream 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(
string shortSha,
out ObjectId oid,
out UnmanagedMemoryStream 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(
ObjectId id,
out int length,
out ObjectType objectType);
///
/// Requests that this backend write an object to the backing store.
///
public abstract int Write(
ObjectId id,
Stream dataStream,
long length,
ObjectType objectType);
///
/// Requests that this backend read an object. Returns a stream so that the caller can read the data in chunks.
///
public abstract int ReadStream(
ObjectId id,
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(ObjectId id);
///
/// Requests that this backend check if an object ID exists. The object ID may not be complete (may be a prefix).
///
public abstract int ExistsPrefix(string shortSha, out ObjectId found);
///
/// 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(ObjectId 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.ExistsPrefix) != 0)
{
nativeBackend.ExistsPrefix = BackendEntryPoints.ExistsPrefixCallback;
}
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.exists_prefix_callback ExistsPrefixCallback = ExistsPrefix;
public static readonly GitOdbBackend.foreach_callback ForEachCallback = Foreach;
public static readonly GitOdbBackend.free_callback FreeCallback = Free;
private static OdbBackend MarshalOdbBackend(IntPtr backendPtr)
{
var intPtr = Marshal.ReadIntPtr(backendPtr, GitOdbBackend.GCHandleOffset);
var odbBackend = GCHandle.FromIntPtr(intPtr).Target as OdbBackend;
if (odbBackend == null)
{
Proxy.giterr_set_str(GitErrorCategory.Reference, "Cannot retrieve the managed OdbBackend.");
return null;
}
return odbBackend;
}
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 = MarshalOdbBackend(backend);
if (odbBackend == null)
{
return (int)GitErrorCode.Error;
}
UnmanagedMemoryStream memoryStream = null;
try
{
ObjectType objectType;
int toReturn = odbBackend.Read(new ObjectId(oid), out memoryStream, out objectType);
if (toReturn != 0)
{
return toReturn;
}
if (memoryStream == null)
{
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);
}
catch (Exception ex)
{
Proxy.giterr_set_str(GitErrorCategory.Odb, ex);
return (int)GitErrorCode.Error;
}
finally
{
if (memoryStream != null)
{
memoryStream.Dispose();
}
}
return (int)GitErrorCode.Ok;
}
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 = MarshalOdbBackend(backend);
if (odbBackend == null)
{
return (int)GitErrorCode.Error;
}
UnmanagedMemoryStream memoryStream = null;
try
{
var shortSha = ObjectId.ToString(short_oid.Id, (int) len);
ObjectId oid;
ObjectType objectType;
int toReturn = odbBackend.ReadPrefix(shortSha, out oid, out memoryStream, out objectType);
if (toReturn != 0)
{
return toReturn;
}
if (memoryStream == null)
{
return (int)GitErrorCode.Error;
}
out_oid.Id = oid.RawId;
len_p = new UIntPtr((ulong)memoryStream.Capacity);
type_p = objectType.ToGitObjectType();
memoryStream.Seek(0, SeekOrigin.Begin);
buffer_p = new IntPtr(memoryStream.PositionPointer);
}
catch (Exception ex)
{
Proxy.giterr_set_str(GitErrorCategory.Odb, ex);
return (int)GitErrorCode.Error;
}
finally
{
if (memoryStream != null)
{
memoryStream.Dispose();
}
}
return (int)GitErrorCode.Ok;
}
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 = MarshalOdbBackend(backend);
if (odbBackend == null)
{
return (int)GitErrorCode.Error;
}
try
{
int length;
ObjectType objectType;
int toReturn = odbBackend.ReadHeader(new ObjectId(oid), out length, out objectType);
if (toReturn != 0)
{
return toReturn;
}
len_p = new UIntPtr((uint)length);
type_p = objectType.ToGitObjectType();
}
catch (Exception ex)
{
Proxy.giterr_set_str(GitErrorCategory.Odb, ex);
return (int)GitErrorCode.Error;
}
return (int)GitErrorCode.Ok;
}
private static unsafe int Write(
IntPtr backend,
ref GitOid oid,
IntPtr data,
UIntPtr len,
GitObjectType type)
{
long length = ConverToLong(len);
OdbBackend odbBackend = MarshalOdbBackend(backend);
if (odbBackend == null)
{
return (int)GitErrorCode.Error;
}
try
{
using (var stream = new UnmanagedMemoryStream((byte*)data, length))
{
return odbBackend.Write(new ObjectId(oid), stream, length, type.ToObjectType());
}
}
catch (Exception ex)
{
Proxy.giterr_set_str(GitErrorCategory.Odb, ex);
return (int)GitErrorCode.Error;
}
}
private static int WriteStream(
out IntPtr stream_out,
IntPtr backend,
long len,
GitObjectType type)
{
stream_out = IntPtr.Zero;
OdbBackend odbBackend = MarshalOdbBackend(backend);
if (odbBackend == null)
{
return (int)GitErrorCode.Error;
}
ObjectType objectType = type.ToObjectType();
try
{
OdbBackendStream stream;
int toReturn = odbBackend.WriteStream(len, objectType, out stream);
if (toReturn == 0)
{
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 = MarshalOdbBackend(backend);
if (odbBackend == null)
{
return (int)GitErrorCode.Error;
}
try
{
OdbBackendStream stream;
int toReturn = odbBackend.ReadStream(new ObjectId(oid), out stream);
if (toReturn == 0)
{
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 = MarshalOdbBackend(backend);
if (odbBackend == null)
{
return false; // Weird
}
try
{
return odbBackend.Exists(new ObjectId(oid));
}
catch (Exception ex)
{
Proxy.giterr_set_str(GitErrorCategory.Odb, ex);
return false;
}
}
private static int ExistsPrefix(
ref GitOid found_oid,
IntPtr backend,
ref GitOid short_oid,
UIntPtr len)
{
OdbBackend odbBackend = MarshalOdbBackend(backend);
if (odbBackend == null)
{
return (int)GitErrorCode.Error;
}
try
{
ObjectId found;
var shortSha = ObjectId.ToString(short_oid.Id, (int)len);
found_oid.Id = ObjectId.Zero.RawId;
int result = odbBackend.ExistsPrefix(shortSha, out found);
if (result == (int) GitErrorCode.Ok)
{
found_oid.Id = found.RawId;
}
return result;
}
catch (Exception ex)
{
Proxy.giterr_set_str(GitErrorCategory.Odb, ex);
return (int)GitErrorCode.Error;
}
}
private static int Foreach(
IntPtr backend,
GitOdbBackend.foreach_callback_callback cb,
IntPtr data)
{
OdbBackend odbBackend = MarshalOdbBackend(backend);
if (odbBackend == null)
{
return (int)GitErrorCode.Error;
}
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)
{
OdbBackend odbBackend = MarshalOdbBackend(backend);
if (odbBackend == null)
{
return;
}
try
{
odbBackend.Free();
var disposable = odbBackend as IDisposable;
if (disposable == null)
{
return;
}
disposable.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 unsafe int CallbackMethod(ObjectId id)
{
var oid = id.RawId;
fixed(void* ptr = &oid[0])
{
return cb(new IntPtr(ptr), data);
}
}
public readonly ForEachCallback ManagedCallback;
private readonly GitOdbBackend.foreach_callback_callback cb;
private readonly IntPtr data;
}
}
internal static long ConverToLong(UIntPtr len)
{
if (len.ToUInt64() > long.MaxValue)
{
throw new InvalidOperationException(
string.Format(
CultureInfo.InvariantCulture,
"Provided length ({0}) exceeds long.MaxValue ({1}).",
len.ToUInt64(), long.MaxValue));
}
return (long)len.ToUInt64();
}
///
/// 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 ExistsPrefix method.
///
ExistsPrefix = 128,
///
/// This OdbBackend declares that it supports the Foreach method.
///
ForEach = 256,
}
///
/// Libgit2 expected backend return codes.
///
protected enum ReturnCode
{
///
/// No error has occured.
///
GIT_OK = 0,
///
/// No object matching the oid, or short oid, can be found in the backend.
///
GIT_ENOTFOUND = -3,
///
/// The given short oid is ambiguous.
///
GIT_EAMBIGUOUS = -5,
///
/// Interruption of the foreach() callback is requested.
///
GIT_EUSER = -7,
}
}
}