Thanks for the response @jens and @borrrden.
Let me apologise in advance for the length of this… I’ll try to keep it as short as I can.
First - two types used to keep track of object properties loaded from the DB, and attachments loaded from the DB or being sent to the DB.
using DBProperties = IDictionary<string, object>;
public class AttachmentInfo
{
public string fieldname;
public string mimetype;
public byte[] data;
}
Now the core routines: Update a document, and Merge in a set of attachments. Merge is what I am trying to optimise to reduce sync traffic.
public string Update(DBProperties new_props, List<AttachmentInfo> attachments = null)
{
log.Debug("About to update {0}", new_props);
if (!new_props.ContainsKey("_id"))
throw new MissingIDException();
var document = database.GetExistingDocument((string)new_props["_id"]);
if (document == null)
throw new MissingDataException();
var rev = document.CreateRevision();
if (rev == null)
throw new FailedUpdateException();
// Set the properties.
MergeProperties(rev.Properties, new_props);
// Set the attachments
if (attachments != null)
MergeAttachments(rev, attachments);
rev.Save();
return document.Id;
}
public void MergeAttachments(UnsavedRevision rev, List<AttachmentInfo> new_attachments)
{
if (rev.Attachments != null)
{
// Create a list of attachments that need to be removed (exist in Attachments, don't exist in new_attachments).
var to_be_removed = new List<string>();
foreach (var att in rev.Attachments)
{
if (new_attachments.Find((AttachmentInfo a) => a.fieldname == att.Name) == null)
to_be_removed.Add(att.Name);
}
// Now go through the attachments removing all that are no longer needed.
foreach (var name in to_be_removed)
rev.RemoveAttachment(name);
}
// Now lets check the incoming attachments...
foreach (var new_att in new_attachments)
{
var old_att = rev.GetAttachment(new_att.fieldname);
if (old_att != null)
{
// The attachment exists in both NEW and OLD attachment lists. So compare the data to see if its changed
var old_data = old_att.Content.ToArray();
// **IF I COMMENT THE IF STMT AND *ALWAYS* SetAttachment, THIS WORKS!**
if (!old_data.SequenceEqual(new_att.data))
// Attachment has changed... so update it.
rev.SetAttachment(new_att.fieldname, new_att.mimetype, new_att.data);
}
else
{
// This is a new attachment. Just add it in.
rev.SetAttachment(new_att.fieldname, new_att.mimetype, new_att.data);
}
}
}
Note the comment above. If I comment out the IF statement (the check to see if the data has changed),
then each attachment is set every time… and this works. I have sat there loading and saving many objects very reliably.
If I uncomment the IF statement so that ONLY changed attachments get SetAttribute, then I get failures, but not at the point of saving. Its at the next attachment load.
The failure mode is very strange, but very reliable. To clarify the process for failure:
- I load an document and its attachments.
- Make a change to the properties
- Attempt to update the document and its attachments
- Load a different document
- Load that document’s attachments.
And here’s the code the loads the attachments for a given document:
public List<AttachmentInfo> GetAttachments(string _id)
{
var document = database.GetExistingDocument(_id);
if (document == null)
{
return null;
}
var revision = document.CurrentRevision;
if (revision == null)
{
return null;
}
var attachments = revision.Attachments;
List<AttachmentInfo> ret = new List<AttachmentInfo>();
if (attachments == null)
return ret;
foreach (var attachment in attachments)
{
var ai = new AttachmentInfo();
ai.fieldname = attachment.Name;
ai.mimetype = attachment.ContentType; <<<< FAILS HERE
ai.data = (byte[])attachment.Content;
ret.Add(ai);
}
return ret;
}
The failure occurs at the line indicated above.
I have uploaded two images of the xamarin debugger showing the contents of attachments at the point of failure, PLUS (and this is where it is a little weird) I have an image of the attachments from the on disk DB (i.e. using CoubaseLiteViewer).
The on disk attachments are completely healthy (there are 7).
The loaded attachments are corrupt - not all 7 of them, just the first few.
So changing that ONE line in the code that sets the attachments during save has the effect of causing the NEXT attachment fetch to fail.
Hope this helps.
Thanks for your help with it.
Paul.