Skip to content

Expose Realm.WriteCopy #1464

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 28, 2017
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@
------------------

### Enhancements
- Exposed `Realm.WriteCopy` API to copy a Realm file and optionally encrypt it with a different key.: ([#1464](https://github.com/realm/realm-dotnet/pull/1464))

### Bug fixes

16 changes: 16 additions & 0 deletions Platform.PCL/Realm.PCL/RealmPCL.cs
Original file line number Diff line number Diff line change
@@ -531,6 +531,22 @@ public void RemoveAll()
RealmPCLHelpers.ThrowProxyShouldNeverBeUsed();
}

/// <summary>
/// Writes a compacted copy of the Realm to the path in the specified config. If the configuration object has
/// non-null <see cref="RealmConfigurationBase.EncryptionKey"/>, the copy will be encrypted with that key.
/// </summary>
/// <remarks>
/// The destination file cannot already exist.
/// <para/>
/// If this is called from within a transaction it writes the current data, and not the data as it was when
/// the last transaction was committed.
/// </remarks>
/// <param name="config">Configuration, specifying the path and optionally the encryption key for the copy.</param>
public void WriteCopy(RealmConfigurationBase config)
{
RealmPCLHelpers.ThrowProxyShouldNeverBeUsed();
}

#region Thread Handover

/// <summary>
9 changes: 9 additions & 0 deletions Realm/Realm/Handles/SharedRealmHandle.cs
Original file line number Diff line number Diff line change
@@ -86,6 +86,9 @@ public static extern IntPtr open(Native.Configuration configuration,

[DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_resolve_query_reference", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr resolve_query_reference(SharedRealmHandle sharedRealm, ThreadSafeReferenceHandle referenceHandle, out NativeException ex);

[DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_write_copy", CallingConvention = CallingConvention.Cdecl)]
public static extern void write_copy(SharedRealmHandle sharedRealm, [MarshalAs(UnmanagedType.LPWStr)] string path, IntPtr path_len, byte[] encryptionKey, out NativeException ex);
}

[Preserve]
@@ -216,6 +219,12 @@ public IntPtr ResolveReference(ThreadSafeReference reference)
return result;
}

public void WriteCopy(string path, byte[] encryptionKey)
{
NativeMethods.write_copy(this, path, (IntPtr)path.Length, encryptionKey, out var nativeException);
nativeException.ThrowIfNecessary();
}

public class SchemaMarshaler
{
public readonly Native.SchemaObject[] Objects;
2 changes: 1 addition & 1 deletion Realm/Realm/Handles/TableHandle.cs
Original file line number Diff line number Diff line change
@@ -164,4 +164,4 @@ public IntPtr Find(SharedRealmHandle realmHandle, long? id)
if (ret ==0 && !myHandle.IsNull() && myHandle != invalidHandle)
mySafeHandle.SetHandle(myHandle);
}// End CER
*/
*/
16 changes: 16 additions & 0 deletions Realm/Realm/Realm.cs
Original file line number Diff line number Diff line change
@@ -1087,6 +1087,22 @@ public void RemoveAll()
}
}

/// <summary>
/// Writes a compacted copy of the Realm to the path in the specified config. If the configuration object has
/// non-null <see cref="RealmConfigurationBase.EncryptionKey"/>, the copy will be encrypted with that key.
/// </summary>
/// <remarks>
/// The destination file cannot already exist.
/// <para/>
/// If this is called from within a transaction it writes the current data, and not the data as it was when
/// the last transaction was committed.
/// </remarks>
/// <param name="config">Configuration, specifying the path and optionally the encryption key for the copy.</param>
public void WriteCopy(RealmConfigurationBase config)
{
SharedRealmHandle.WriteCopy(config.DatabasePath, config.EncryptionKey);
}

#region Transactions

internal void DrainTransactionQueue()
9 changes: 1 addition & 8 deletions Tests/Tests.Shared/ConfigurationTests.cs
Original file line number Diff line number Diff line change
@@ -46,13 +46,6 @@ protected override void CustomTearDown()
Realm.DeleteRealm(_configuration);
}

private static void ReliesOnEncryption()
{
#if ENCRYPTION_DISABLED
Assert.Ignore("This test relies on encryption which is not enabled in this build");
#endif
}

[Test]
public void DefaultConfigurationShouldHaveValidPath()
{
@@ -210,7 +203,7 @@ public void AbleToReopenEncryptedWithSameKey()
config2.EncryptionKey[0] = 42;

// Assert
Assert.That(() =>
Assert.That(() =>
{
using (Realm.GetInstance(config2))
{
68 changes: 68 additions & 0 deletions Tests/Tests.Shared/InstanceTests.cs
Original file line number Diff line number Diff line change
@@ -646,6 +646,74 @@ public void GetInstanceAsync_ExecutesMigrationsInBackground()
});
}

[TestCase(true, true)]
[TestCase(true, false)]
[TestCase(false, true)]
[TestCase(false, false)]
public void WriteEncryptedCopy_WhenEncryptionKeyProvided_WritesACopy(bool originalEncrypted, bool copyEncrypted)
{
if (originalEncrypted || copyEncrypted)
{
ReliesOnEncryption();
}

var originalConfig = new RealmConfiguration(Path.GetTempFileName());
if (originalEncrypted)
{
originalConfig.EncryptionKey = new byte[64];
originalConfig.EncryptionKey[0] = 42;
}

var copyConfig = new RealmConfiguration(Path.GetTempFileName());
if (copyEncrypted)
{
copyConfig.EncryptionKey = new byte[64];
copyConfig.EncryptionKey[0] = 14;
}

File.Delete(copyConfig.DatabasePath);

try
{
using (var original = Realm.GetInstance(originalConfig))
{
original.Write(() =>
{
original.Add(new Person
{
FirstName = "John",
LastName = "Doe"
});

original.WriteCopy(copyConfig);

using (var copy = Realm.GetInstance(copyConfig))
{
var copiedPerson = copy.All<Person>().SingleOrDefault();
Assert.That(copiedPerson, Is.Not.Null);
Assert.That(copiedPerson.FirstName, Is.EqualTo("John"));
Assert.That(copiedPerson.LastName, Is.EqualTo("Doe"));
}

if (copyEncrypted)
{
var invalidConfig = new RealmConfiguration(copyConfig.DatabasePath)
{
EncryptionKey = originalConfig.EncryptionKey
};

Assert.That(() => Realm.GetInstance(invalidConfig), Throws.TypeOf<RealmFileAccessErrorException>());
}
});
}
}
finally
{
Realm.DeleteRealm(originalConfig);
Realm.DeleteRealm(copyConfig);
}
}

private static void AddDummyData(Realm realm)
{
for (var i = 0; i < 1000; i++)
7 changes: 7 additions & 0 deletions Tests/Tests.Shared/RealmTest.cs
Original file line number Diff line number Diff line change
@@ -63,5 +63,12 @@ public void TearDown()
protected virtual void CustomTearDown()
{
}

protected static void ReliesOnEncryption()
{
#if ENCRYPTION_DISABLED
Assert.Ignore("This test relies on encryption which is not enabled in this build");
#endif
}
}
}
10 changes: 10 additions & 0 deletions wrappers/src/shared_realm_cs.cpp
Original file line number Diff line number Diff line change
@@ -248,4 +248,14 @@ REALM_EXPORT void thread_safe_reference_destroy(ThreadSafeReferenceBase* referen
delete reference;
}

REALM_EXPORT void shared_realm_write_copy(SharedRealm* realm, uint16_t* path, size_t path_len, char* encryption_key, NativeException::Marshallable& ex)
{
handle_errors(ex, [&]() {
Utf16StringAccessor pathStr(path, path_len);

// by definition the key is only allowed to be 64 bytes long, enforced by C# code
realm->get()->write_copy(pathStr, BinaryData(encryption_key, encryption_key ? 64 : 0));
});
}

}