Skip to content
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

Made SayMore verify saved EAF and meta files to ensure that they have valid contents and are readable. #186

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
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 SayMore.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=Dropbox/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=fsutil/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Insite/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=malware/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Mets/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=NTSC/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Palaso/@EntryIndexedValue">True</s:Boolean>
Expand Down
71 changes: 48 additions & 23 deletions src/SayMore/Model/Files/XmlFileSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using SayMore.Model.Fields;
using SIL.Xml;
using SayMore.Utilities;
using SIL.Extensions;

namespace SayMore.Model.Files
{
Expand All @@ -33,7 +34,7 @@ public XmlFileSerializer(IDictionary<string, IXmlFieldSerializer> xmlFieldSerial
}

/// ------------------------------------------------------------------------------------
public void Save(IEnumerable<FieldInstance> fields, string path, string rootElementName)
public void Save(IEnumerable<FieldInstance> fields, string path, string rootElementName, bool verify = true)
{
var giveUpTime = DateTime.Now.AddSeconds(15);

Expand Down Expand Up @@ -116,11 +117,29 @@ public void Save(IEnumerable<FieldInstance> fields, string path, string rootElem
{
GC.Collect();
root.Save(path);
// This is an attempt to put an end to problems like SP-2337, etc (search Jira
// for "0x00"), where an empty XML file is getting created. We have no actual
// evidence that SayMore is actually creating the empty files, but it seems
// improbable that it would be happening to more than 1 or 2 users if it were
// being caused by some other software or malware. I'm wondering if it could be
// caused by a crash or power failure during the process of saving. If so, this
// might not help, but it could force the disk IO buffer to flush or at least
// maybe give us some additional insight as to when this is happening. If this
// does not stop the problem, then maybe we need to start saving a local backup
// before each file is saved and use the backup as a fallback when loading
// (along with a warning to alert the user that something might have gotten
// lost).
if (verify)
{
var xmlRootForVerification = GetXmlDocumentRoot(path).GetXElement();
if (!root.Nodes().Select(e => e.ToString()).SetEquals(xmlRootForVerification.Nodes().Select(v => v.ToString())))
throw new IOException("File contents not saved correctly: " + path);
}
break;
}
catch (IOException e)
catch (Exception e)
{
if (e.GetType() != typeof(IOException))
if (e.GetType() != typeof(IOException) && !(e is XmlException))
throw;

// Guarantee that it retries at least once
Expand Down Expand Up @@ -204,6 +223,31 @@ public void Load(/*TODO: ClearShare.Work work,*/ List<FieldInstance> fields, str
{
fields.Clear();

var root = GetXmlDocumentRoot(path);

fields.AddRange(root.ChildNodes.Cast<XmlNode>()
.Select(node => GetFieldFromNode(node, fileType.GetIsCustomFieldId))
.Where(fieldInstance => fieldInstance != null));

var customFieldList = root.SelectSingleNode(kCustomFieldsElement);
if (customFieldList != null)
{
fields.AddRange(customFieldList.ChildNodes.Cast<XmlNode>()
.Select(node => new FieldInstance(kCustomFieldIdPrefix + node.Name, FieldInstance.kStringType,
CleanupLineBreaks(node.InnerText))));
}

var additionalFieldList = root.SelectSingleNode(kAdditionalFieldsElement);
if (additionalFieldList != null)
{
fields.AddRange(additionalFieldList.ChildNodes.Cast<XmlNode>()
.Select(node => new FieldInstance(kAdditionalFieldIdPrefix + node.Name, FieldInstance.kStringType,
CleanupLineBreaks(node.InnerText))));
}
}

private static XmlNode GetXmlDocumentRoot(string path)
{
var doc = new XmlDocument();

var giveUpTime = DateTime.Now.AddSeconds(4);
Expand Down Expand Up @@ -235,26 +279,7 @@ public void Load(/*TODO: ClearShare.Work work,*/ List<FieldInstance> fields, str
}
}

var root = doc.ChildNodes[1];
fields.AddRange(root.ChildNodes.Cast<XmlNode>()
.Select(node => GetFieldFromNode(node, fileType.GetIsCustomFieldId))
.Where(fieldInstance => fieldInstance != null));

var customFieldList = root.SelectSingleNode(kCustomFieldsElement);
if (customFieldList != null)
{
fields.AddRange(customFieldList.ChildNodes.Cast<XmlNode>()
.Select(node => new FieldInstance(kCustomFieldIdPrefix + node.Name, FieldInstance.kStringType,
CleanupLineBreaks(node.InnerText))));
}

var additionalFieldList = root.SelectSingleNode(kAdditionalFieldsElement);
if (additionalFieldList != null)
{
fields.AddRange(additionalFieldList.ChildNodes.Cast<XmlNode>()
.Select(node => new FieldInstance(kAdditionalFieldIdPrefix + node.Name, FieldInstance.kStringType,
CleanupLineBreaks(node.InnerText))));
}
return doc.ChildNodes[1];
}

/// ------------------------------------------------------------------------------------
Expand Down
8 changes: 4 additions & 4 deletions src/SayMore/ProjectContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ public static void SetContributorsListToSession(string sessionsFolder)
(Func<string, bool>)(fileName => true) :
fileName => !Path.GetFileName(fileName).StartsWith(ProjectElement.kMacOsxResourceFilePrefix);
var metaFilesList = filesInDir.Where(f => doesNotHaveIllegalPrefix(f) &&
f.EndsWith(Settings.Default.MetadataFileExtension)).ToList();
f.EndsWith(Settings.Default.MetadataFileExtension) && !f.Contains(Settings.Default.OralAnnotationGeneratedFileSuffix)).ToList();
var sessionDoc = LoadXmlDocument(sessionFile);
LoadContributors(sessionDoc, namesList, nameRolesList, contributorLists);
var root = sessionDoc.DocumentElement;
Expand Down Expand Up @@ -214,10 +214,10 @@ private static void LoadContributors(XmlNode xmlDoc, SortedSet<string> namesList
// we need to migrate it. It might also have been edited outside SayMore and have
// participants that are not known contributors, even though it has a contributions element.
// So add in any participants we don't already have in some form.
var particpantsNode = xmlDoc.SelectSingleNode("//participants");
if (particpantsNode == null)
var participantsNode = xmlDoc.SelectSingleNode("//participants");
if (participantsNode == null)
return;
foreach (var participant in particpantsNode.InnerText.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
foreach (var participant in participantsNode.InnerText.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
{
var name = participant.Trim();

Expand Down
2 changes: 2 additions & 0 deletions src/SayMore/Transcription/Model/TierCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,8 @@ public string Save()
public string Save(string annotatedMediaFile)
{
return AnnotationFileHelper.Save(annotatedMediaFile, this);
// TODO (SP-2326, SP-2336, etc.): Verify that saved EAF file has contents and is
// valid/readable.
}

#endregion
Expand Down
16 changes: 10 additions & 6 deletions src/SayMore/Utilities/ExtensionMethods.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@

using System.Reflection;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Linq;
using ComboBox = System.Windows.Forms.ComboBox;
using TextBox = System.Windows.Forms.TextBox;
using DatePicker = SayMore.UI.LowLevelControls.DatePicker;
Expand All @@ -9,12 +11,6 @@ namespace SayMore.Utilities
{
internal static class ExtensionMethods
{
public static MethodInfo HasMethod(this object objectToCheck, string methodName)
{
var type = objectToCheck.GetType();
return type.GetMethod(methodName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
}

public static bool IsValidBirthYear(this string birthYear)
{
var val = birthYear.Trim();
Expand Down Expand Up @@ -70,5 +66,13 @@ public static bool IsMotionPicture(this SIL.Media.MediaInfo silMediaInfo)
silMediaInfo.AnalysisData.PrimaryVideoStream.AvgFrameRate > 0 ||
silMediaInfo.AnalysisData.PrimaryVideoStream.FrameRate > 0);
}

public static XElement GetXElement(this XmlNode node)
{
XDocument xDoc = new XDocument();
using (XmlWriter xmlWriter = xDoc.CreateWriter())
node.WriteTo(xmlWriter);
return xDoc.Root;
}
}
}
4 changes: 2 additions & 2 deletions src/SayMoreTests/model/Files/FileSerializerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ public void Load_FileDoesNotExist_Throws()
public void Save_DirectoryNotFound_Throws()
{
Assert.Throws<DirectoryNotFoundException>(() =>
_serializer.Save(_fields, _parentFolder.Combine("notthere", "test.txt"), "x"));
_serializer.Save(_fields, _parentFolder.Combine("notthere", "test.txt"), "x", false));
}

/// ------------------------------------------------------------------------------------
Expand Down Expand Up @@ -353,7 +353,7 @@ private void DoRoundTrip()
/// ------------------------------------------------------------------------------------
private void SaveToStandardPlace()
{
_serializer.Save(_fields, _parentFolder.Combine("test.txt"), "x");
_serializer.Save(_fields, _parentFolder.Combine("test.txt"), "x", false);
}

/// ------------------------------------------------------------------------------------
Expand Down