So,
What i want:
To be able to tell Couchbase Lite (CB Lite) where to store the data and implement SQLCipher encryption on it.
What is my problem:
Even though i have the encryption working on the default directory, as soon as i tell the Manager to use a custom directory it throws an exception when trying to create the database: Message:
"Error 8, 1032 (attempt to write a readonly database) executing sql ' CREATE INDEX docs_docid ON docs(docid)'"
Source:
"Couchbase.Lite.Storage.SQLCipher"
StackTrace:
"at Couchbase.Lite.Storage.SQLCipher.SqlitePCLRawStorageEngine.ExecSQL (System.String sql, SQLitePCL.sqlite3 db, System.Object[] paramArgs) [0x00079] in /Users/jenkins/jenkins/workspace/couchbase-lite-net-build@2/1.4/Android/couchbase-lite-net/src/StorageEngines/SQLiteCommon/storage.sqlite.common/src/DatabaseUpgraderFactory_Upgraders.cs:249 \n at Couchbase.Lite.Storage.SQLCipher.SqlitePCLRawStorageEngine.ExecSQL (System.String sql, System.Object[] paramArgs) <0xd70821f8 + 0x0005f> in <223c4357c0d44faaaa01c387793a30cc>:0 \n at Couchbase.Lite.Storage.SQLCipher.SqlitePCLRawStorageEngine.Open (System.String path, System.Boolean readOnly, System.String schema, Couchbase.Lite.Store.SymmetricKey encryptionKey) [0x00065] in /Users/jenkins/jenkins/workspace/couchbase-lite-net-build@2/1.4/Android/couchbase-lite-net/src/StorageEngines/SQLiteCommon/storage.sqlite.common/src/DatabaseUpgraderFactory.cs:87 "
Now, The strange thing is that when running my code on the emulator, it throws no exception. The problem comes only when running my code on a real device (Lenovo TAB 2A Series, Android 5.0.1).
Note: Even though the exception is thrown the custom folder is created, and the couchbase lite folder as well, but inside it is only to be found a file called db.sqlite3. Normally three files and a folder are created:
Note 2: I had added the WRITE_EXTERNAL_STORAGE permission to the Android Manifest.
The code that runs with no problem:
public async Task<Database> OpenDatabaseOnEncryptedWayAsync(string encryptionPassword)
{
Database encryptedDatabase = null;
try
{
SymmetricKey Key;
DatabaseOptions options = new DatabaseOptions();
await Task.Run(() =>
{
Key = new SymmetricKey(encryptionPassword);
options.EncryptionKey = Key;
options.Create = true;
options.StorageType = StorageEngineTypes.SQLite;
});
// Database stored in default directory
encryptedDatabase = Manager.SharedInstance.OpenDatabase(database, options);
}
catch (CouchbaseLiteException e)
{
LogWriterMethod.TryWriteLog(String.Format("Error while creating encrypted database: {0}", e));
Console.WriteLine("Encryption Message: {0}", e);
}
catch (Exception e)
{
LogWriterMethod.TryWriteLog(String.Format("Unhandled Exception on OpenDatabaseOnEncryptedWayAsync method: {0}", e));
Console.WriteLine("Unhandled Exception Message: {0}", e);
}
App.SettingsViewModel.isDatabaseOpen = encryptedDatabase != null;
return encryptedDatabase;
}
The code that throws the (CouchbaseLite)exception:
public async Task<Database> OpenDatabaseOnEncryptedWayAsync(string encryptionPassword)
{
Database encryptedDatabase = null;
try
{
SymmetricKey Key;
DatabaseOptions options = new DatabaseOptions();
await Task.Run(() =>
{
Key = new SymmetricKey(encryptionPassword);
options.EncryptionKey = Key;
options.Create = true;
options.StorageType = StorageEngineTypes.SQLite;
});
// Custom directory
var path = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads).Path;
var dir = Directory.CreateDirectory(path);
Manager manager = new Manager(dir, Manager.DefaultOptions);
encryptedDatabase = manager.GetDatabase(database);
}
catch (CouchbaseLiteException e)
{
LogWriterMethod.TryWriteLog(String.Format("Error while creating encrypted database: {0}", e));
Console.WriteLine("Encryption Message: {0}", e);
}
catch (Exception e)
{
LogWriterMethod.TryWriteLog(String.Format("Unhandled Exception on OpenDatabaseOnEncryptedWayAsync method: {0}", e));
Console.WriteLine("Unhandled Exception Message: {0}", e);
}
App.SettingsViewModel.isDatabaseOpen = encryptedDatabase != null;
return encryptedDatabase;
}
So, does anybody out there knows how to deal with this issue? Thanks in advance :)
Comming back to the topic, i found a very simple error in the code that throws the (CouchbaseLite)Exception. I share it in case anyone reading this falls in the same error:
I am creating the directory and then the manager using the new path, but i never assign the DatabaseOptions to the newly created Manager
SymmetricKey Key;
DatabaseOptions options = new DatabaseOptions();
await Task.Run(() =>
{
Key = new SymmetricKey(encryptionPassword);
options.EncryptionKey = Key;
options.Create = true;
options.StorageType = StorageEngineTypes.SQLite;
});
var path = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads).Path;
var dir = Directory.CreateDirectory(path);
Manager manager = new Manager(dir, Manager.DefaultOptions);
encryptedDatabase = manager.GetDatabase(database);
So, all we have to do is to change the code to read
encryptedDatabase = manager.OpenDatabase(database, options);
And that's it!