public void Action_WithOneModifiedEntry_ShouldCountOneModified() { // Setup var lfProj = _lfProj; sutFdoToMongo.Run(lfProj); Guid entryGuid = Guid.Parse(TestEntryGuidStr); LfLexEntry entry = _conn.GetLfLexEntryByGuid(entryGuid); FdoCache cache = lfProj.FieldWorksProject.Cache; string vernacularWS = cache.LanguageProject.DefaultVernacularWritingSystem.Id; string changedLexeme = "modified lexeme for this test"; entry.Lexeme = LfMultiText.FromSingleStringMapping(vernacularWS, changedLexeme); entry.AuthorInfo = new LfAuthorInfo(); entry.AuthorInfo.ModifiedDate = DateTime.UtcNow; _conn.UpdateMockLfLexEntry(entry); // Exercise sutMongoToFdo.Run(lfProj); // Verify Assert.That(_counts.Added, Is.EqualTo(0)); Assert.That(_counts.Modified, Is.EqualTo(1)); Assert.That(_counts.Deleted, Is.EqualTo(0)); Assert.That(LfMergeBridgeServices.FormatCommitMessageForLfMerge(_counts.Added, _counts.Modified, _counts.Deleted), Is.EqualTo("Language Forge: 1 entry modified")); }
/// <summary> /// Convert FDO example LF example. /// </summary> /// <returns>LF example /// <param name="fdoExample">Fdo example.</param> private LfExample FdoExampleToLfExample(ILexExampleSentence fdoExample) { var lfExample = new LfExample(); ILgWritingSystem AnalysisWritingSystem = ServiceLocator.LanguageProject.DefaultAnalysisWritingSystem; ILgWritingSystem VernacularWritingSystem = ServiceLocator.LanguageProject.DefaultVernacularWritingSystem; lfExample.Guid = fdoExample.Guid; lfExample.Sentence = ToMultiText(fdoExample.Example); lfExample.Reference = LfMultiText.FromSingleITsString(fdoExample.Reference, ServiceLocator.WritingSystemFactory); // ILexExampleSentence fields we currently do not convert: // fdoExample.DoNotPublishInRC; // fdoExample.LiftResidue; // fdoExample.PublishIn; // NOTE: Currently, LanguageForge only stores one translation per example, whereas FDO can store // multiple translations with (possibly) different statuses (as freeform strings, like "old", "updated", // "needs fixing"...). Until LanguageForge acquires a data model where translations are stored in a list, // we will save only the first translation (if any) to Mongo. We also save the GUID so that the Mongo->FDO // direction will know which ICmTranslation object to update with any changes. // TODO: Once LF improves its data model for translations, persist all of them instead of just the first. foreach (ICmTranslation translation in fdoExample.TranslationsOC.Take(1)) { lfExample.Translation = ToMultiText(translation.Translation); lfExample.TranslationGuid = translation.Guid; } BsonDocument customFieldsAndGuids = _convertCustomField.GetCustomFieldsForThisCmObject(fdoExample, "examples", ListConverters); BsonDocument customFieldsBson = customFieldsAndGuids["customFields"].AsBsonDocument; BsonDocument customFieldGuids = customFieldsAndGuids["customFieldGuids"].AsBsonDocument; lfExample.CustomFields = customFieldsBson; lfExample.CustomFieldGuids = customFieldGuids; return(lfExample); }
public void Action_WithOneNewEntry_ShouldCountOneAdded() { // Setup var lfProj = _lfProj; LfLexEntry newEntry = new LfLexEntry(); newEntry.Guid = Guid.NewGuid(); FdoCache cache = lfProj.FieldWorksProject.Cache; string vernacularWS = cache.LanguageProject.DefaultVernacularWritingSystem.Id; string newLexeme = "new lexeme for this test"; newEntry.Lexeme = LfMultiText.FromSingleStringMapping(vernacularWS, newLexeme); newEntry.AuthorInfo = new LfAuthorInfo(); newEntry.AuthorInfo.CreatedDate = DateTime.UtcNow; newEntry.AuthorInfo.ModifiedDate = newEntry.AuthorInfo.CreatedDate; _conn.UpdateMockLfLexEntry(newEntry); // Exercise sutMongoToFdo.Run(lfProj); // Verify Assert.That(_counts.Added, Is.EqualTo(1)); Assert.That(_counts.Modified, Is.EqualTo(0)); Assert.That(_counts.Deleted, Is.EqualTo(0)); Assert.That(LfMergeBridgeServices.FormatCommitMessageForLfMerge(_counts.Added, _counts.Modified, _counts.Deleted), Is.EqualTo("Language Forge: 1 entry added")); }
public static LfMultiText ToMultiText(IMultiAccessorBase fdoMultiString, ILgWritingSystemFactory fdoWritingSystemManager) { if ((fdoMultiString == null) || (fdoWritingSystemManager == null)) { return(null); } return(LfMultiText.FromFdoMultiString(fdoMultiString, fdoWritingSystemManager)); }
private LfMultiText ToMultiText(IMultiAccessorBase fdoMultiString) { if (fdoMultiString == null) { return(null); } return(LfMultiText.FromFdoMultiString(fdoMultiString, ServiceLocator.WritingSystemManager)); }
public static LfMultiText ToMultiText(IMultiAccessorBase LcmMultiString, ILgWritingSystemFactory LcmWritingSystemManager) { if ((LcmMultiString == null) || (LcmWritingSystemManager == null)) { return(null); } return(LfMultiText.FromLcmMultiString(LcmMultiString, LcmWritingSystemManager)); }
public void Action_RunTwiceWithOneNewEntryEachTime_ShouldCountTwoAddedInTotal() { // Setup var lfProj = _lfProj; LfLexEntry newEntry = new LfLexEntry(); newEntry.Guid = Guid.NewGuid(); LcmCache cache = lfProj.FieldWorksProject.Cache; string vernacularWS = cache.LanguageProject.DefaultVernacularWritingSystem.Id; string newLexeme = "new lexeme for this test"; newEntry.Lexeme = LfMultiText.FromSingleStringMapping(vernacularWS, newLexeme); newEntry.AuthorInfo = new LfAuthorInfo(); newEntry.AuthorInfo.CreatedDate = DateTime.UtcNow; newEntry.AuthorInfo.ModifiedDate = newEntry.AuthorInfo.CreatedDate; _conn.UpdateMockLfLexEntry(newEntry); // Exercise SutMongoToLcm.Run(lfProj); // Verify Assert.That(_counts.Added, Is.EqualTo(1)); Assert.That(_counts.Modified, Is.EqualTo(0)); Assert.That(_counts.Deleted, Is.EqualTo(0)); Assert.That(LfMergeBridgeServices.FormatCommitMessageForLfMerge(_counts.Added, _counts.Modified, _counts.Deleted), Is.EqualTo("Language Forge: 1 entry added")); // Setup second run newEntry = new LfLexEntry(); newEntry.Guid = Guid.NewGuid(); newLexeme = "second new lexeme for this test"; newEntry.Lexeme = LfMultiText.FromSingleStringMapping(vernacularWS, newLexeme); newEntry.AuthorInfo = new LfAuthorInfo(); newEntry.AuthorInfo.CreatedDate = DateTime.UtcNow; newEntry.AuthorInfo.ModifiedDate = newEntry.AuthorInfo.CreatedDate; _conn.UpdateMockLfLexEntry(newEntry); // Exercise SutMongoToLcm.Run(lfProj); // Verify Assert.That(_counts.Added, Is.EqualTo(1)); // Modified and Deleted shouldn't have changed, but check Added first // since that's the main point of this test Assert.That(_counts.Modified, Is.EqualTo(0)); Assert.That(_counts.Deleted, Is.EqualTo(0)); Assert.That(LfMergeBridgeServices.FormatCommitMessageForLfMerge(_counts.Added, _counts.Modified, _counts.Deleted), Is.EqualTo("Language Forge: 1 entry added")); }
public void SynchronizeAction_LFDataChangedLDEntryDeleted_LFWins() { // Setup TestEnvironment.CopyFwProjectTo(modifiedTestProjectCode, _lDSettings.WebWorkDirectory); Directory.Move(Path.Combine(_lDSettings.WebWorkDirectory, modifiedTestProjectCode), LanguageDepotMock.ProjectFolderPath); _lfProject.IsInitialClone = true; _transferFdoToMongo.Run(_lfProject); IEnumerable <LfLexEntry> originalMongoData = _mongoConnection.GetLfLexEntries(); LfLexEntry lfEntry = originalMongoData.First(e => e.Guid == _testDeletedEntryGuid); DateTime originalLfDateModified = lfEntry.DateModified; Assert.That(lfEntry.Senses.Count, Is.EqualTo(1)); const string lfCreatedGloss = "new English gloss - added in LF"; const string fwChangedGloss = "English gloss - changed in FW"; // LF adds a gloss to the entry that LD is deleting lfEntry.Senses[0].Gloss = LfMultiText.FromSingleStringMapping("en", lfCreatedGloss); lfEntry.AuthorInfo.ModifiedDate = DateTime.UtcNow; _mongoConnection.UpdateRecord(_lfProject, lfEntry); _lDProject = new LanguageDepotMock(testProjectCode, _lDSettings); var lDcache = _lDProject.FieldWorksProject.Cache; Assert.That(() => lDcache.ServiceLocator.GetObject(_testDeletedEntryGuid), Throws.InstanceOf <KeyNotFoundException>()); var lDFdoEntry = lDcache.ServiceLocator.GetObject(_testEntryGuid) as ILexEntry; Assert.That(lDFdoEntry.SensesOS[0].Gloss.AnalysisDefaultWritingSystem.Text, Is.EqualTo(fwChangedGloss)); DateTime originalLdDateModified = lDFdoEntry.DateModified; // Exercise var sutSynchronize = new SynchronizeAction(_env.Settings, _env.Logger); var timeBeforeRun = DateTime.UtcNow; sutSynchronize.Run(_lfProject); // Verify modified LF entry wins Assert.That(GetGlossFromMongoDb(_testDeletedEntryGuid, originalNumOfFdoEntries + 1, 0), Is.EqualTo(lfCreatedGloss)); Assert.That(GetGlossFromMongoDb(_testEntryGuid, originalNumOfFdoEntries + 1, 0), Is.EqualTo(fwChangedGloss)); Assert.That(GetGlossFromLanguageDepot(_testDeletedEntryGuid, 1), Is.EqualTo(lfCreatedGloss)); LfLexEntry updatedLfEntry = _mongoConnection.GetLfLexEntries().First(e => e.Guid == _testEntryGuid); DateTime updatedLfDateModified = updatedLfEntry.DateModified; Assert.That(updatedLfDateModified, Is.GreaterThan(originalLfDateModified)); Assert.That(updatedLfDateModified, Is.GreaterThan(originalLdDateModified)); Assert.That(_mongoConnection.GetLastSyncedDate(_lfProject), Is.GreaterThanOrEqualTo(timeBeforeRun)); }
public void Action_RunTwiceWithTheSameEntryModifiedEachTime_ShouldCountTwoModifiedInTotal() { // Setup var lfProj = _lfProj; sutFdoToMongo.Run(lfProj); Guid entryGuid = Guid.Parse(TestEntryGuidStr); LfLexEntry entry = _conn.GetLfLexEntryByGuid(entryGuid); FdoCache cache = lfProj.FieldWorksProject.Cache; string vernacularWS = cache.LanguageProject.DefaultVernacularWritingSystem.Id; string changedLexeme = "modified lexeme for this test"; entry.Lexeme = LfMultiText.FromSingleStringMapping(vernacularWS, changedLexeme); entry.AuthorInfo = new LfAuthorInfo(); entry.AuthorInfo.ModifiedDate = DateTime.UtcNow; _conn.UpdateMockLfLexEntry(entry); // Exercise sutMongoToFdo.Run(lfProj); // Verify Assert.That(_counts.Added, Is.EqualTo(0)); Assert.That(_counts.Modified, Is.EqualTo(1)); Assert.That(_counts.Deleted, Is.EqualTo(0)); Assert.That(LfMergeBridgeServices.FormatCommitMessageForLfMerge(_counts.Added, _counts.Modified, _counts.Deleted), Is.EqualTo("Language Forge: 1 entry modified")); // Setup second run string changedLexeme2 = "second modified lexeme for this test"; entry.Lexeme = LfMultiText.FromSingleStringMapping(vernacularWS, changedLexeme2); entry.AuthorInfo = new LfAuthorInfo(); entry.AuthorInfo.ModifiedDate = DateTime.UtcNow; _conn.UpdateMockLfLexEntry(entry); // Exercise second run sutMongoToFdo.Run(lfProj); // Verify second run Assert.That(_counts.Modified, Is.EqualTo(1)); // Added and Deleted shouldn't have changed, but check Modified first // since that's the main point of this test Assert.That(_counts.Added, Is.EqualTo(0)); Assert.That(_counts.Deleted, Is.EqualTo(0)); Assert.That(LfMergeBridgeServices.FormatCommitMessageForLfMerge(_counts.Added, _counts.Modified, _counts.Deleted), Is.EqualTo("Language Forge: 1 entry modified")); }
public void SynchronizeAction_LFDataChangedLDOtherDataChanged_ModifiedDateUpdated() { //Setup TestEnvironment.CopyFwProjectTo(modifiedTestProjectCode, _lDSettings.WebWorkDirectory); Directory.Move(Path.Combine(_lDSettings.WebWorkDirectory, modifiedTestProjectCode), LanguageDepotMock.ProjectFolderPath); _lfProject.IsInitialClone = true; _transferFdoToMongo.Run(_lfProject); var lfEntry = _mongoConnection.GetLfLexEntries().First(e => e.Guid == _testEntryGuid); var originalLfDateModified = lfEntry.DateModified; var originalLfAuthorInfoModifiedDate = lfEntry.AuthorInfo.ModifiedDate; lfEntry.Note = LfMultiText.FromSingleStringMapping("en", "A note from LF"); lfEntry.AuthorInfo.ModifiedDate = DateTime.UtcNow; _mongoConnection.UpdateRecord(_lfProject, lfEntry); var fwChangedGloss = lfEntry.Senses[0].Gloss["en"].Value + " - changed in FW"; _lDProject = new LanguageDepotMock(testProjectCode, _lDSettings); var lDcache = _lDProject.FieldWorksProject.Cache; var lDFdoEntry = lDcache.ServiceLocator.GetObject(_testEntryGuid) as ILexEntry; Assert.That(lDFdoEntry.SensesOS[0].Gloss.AnalysisDefaultWritingSystem.Text, Is.EqualTo(fwChangedGloss)); // Exercise var sutSynchronize = new SynchronizeAction(_env.Settings, _env.Logger); var timeBeforeRun = DateTime.UtcNow; sutSynchronize.Run(_lfProject); // Verify Assert.That(GetGlossFromMongoDb(_testEntryGuid), Is.EqualTo(fwChangedGloss)); var updatedLfEntry = _mongoConnection.GetLfLexEntries().First(e => e.Guid == _testEntryGuid); // LF and LD changed the same entry but different fields, so we want to see the // DateModified change so that LF will re-process the entry. Assert.That(updatedLfEntry.DateModified, Is.GreaterThan(originalLfDateModified)); // The FDO modified date (AuthorInfo.ModifiedDate in LF) should be updated. Assert.That(updatedLfEntry.AuthorInfo.ModifiedDate, Is.GreaterThan(originalLfAuthorInfoModifiedDate)); Assert.That(_mongoConnection.GetLastSyncedDate(_lfProject), Is.GreaterThanOrEqualTo(timeBeforeRun)); }
/// <summary> /// Convert FDO sense to LF sense. /// </summary> /// <returns>LF sense /// <param name="fdoSense">Fdo sense.</param> private LfSense FdoSenseToLfSense(ILexSense fdoSense) { var lfSense = new LfSense(); ILgWritingSystem VernacularWritingSystem = ServiceLocator.LanguageProject.DefaultVernacularWritingSystem; ILgWritingSystem AnalysisWritingSystem = ServiceLocator.LanguageProject.DefaultAnalysisWritingSystem; lfSense.Guid = fdoSense.Guid; lfSense.Gloss = ToMultiText(fdoSense.Gloss); lfSense.Definition = ToMultiText(fdoSense.Definition); // Fields below in alphabetical order by ILexSense property, except for Guid, Gloss and Definition lfSense.AcademicDomains = ToStringArrayField(AcademicDomainListCode, fdoSense.DomainTypesRC); lfSense.AnthropologyCategories = ToStringArrayField(AnthroCodeListCode, fdoSense.AnthroCodesRC); lfSense.AnthropologyNote = ToMultiText(fdoSense.AnthroNote); lfSense.DiscourseNote = ToMultiText(fdoSense.DiscourseNote); lfSense.EncyclopedicNote = ToMultiText(fdoSense.EncyclopedicInfo); if (fdoSense.ExamplesOS != null) { lfSense.Examples = new List <LfExample>(fdoSense.ExamplesOS.Select(FdoExampleToLfExample)); } lfSense.GeneralNote = ToMultiText(fdoSense.GeneralNote); lfSense.GrammarNote = ToMultiText(fdoSense.GrammarNote); if (fdoSense.LIFTid == null) { lfSense.LiftId = null; } else { lfSense.LiftId = fdoSense.LIFTid.Normalize(System.Text.NormalizationForm.FormC); // Because LIFT files on disk are NFC and we need to make sure LiftIDs match those on disk } if (fdoSense.MorphoSyntaxAnalysisRA != null) { IPartOfSpeech secondaryPos = null; // Only used in derivational affixes IPartOfSpeech pos = ConvertFdoToMongoPartsOfSpeech.FromMSA(fdoSense.MorphoSyntaxAnalysisRA, out secondaryPos); // Sometimes the part of speech can be null for legitimate reasons, so check the known class IDs before warning of an unknown MSA type if (pos == null && !ConvertFdoToMongoPartsOfSpeech.KnownMsaClassIds.Contains(fdoSense.MorphoSyntaxAnalysisRA.ClassID)) { Logger.Warning("Got MSA of unknown type {0} in sense {1} in project {2}", fdoSense.MorphoSyntaxAnalysisRA.GetType().Name, fdoSense.Guid, LfProject.ProjectCode); } else { lfSense.PartOfSpeech = ToStringField(GrammarListCode, pos); lfSense.SecondaryPartOfSpeech = ToStringField(GrammarListCode, secondaryPos); // It's fine if secondaryPos is still null here } } lfSense.PhonologyNote = ToMultiText(fdoSense.PhonologyNote); if (fdoSense.PicturesOS != null) { lfSense.Pictures = new List <LfPicture>(fdoSense.PicturesOS.Select(FdoPictureToLfPicture)); //Use the commented code for debugging into FdoPictureToLfPicture // //lfSense.Pictures = new List<LfPicture>(); //foreach (var fdoPic in fdoSense.PicturesOS) // lfSense.Pictures.Add(FdoPictureToLfPicture(fdoPic)); } lfSense.SenseBibliography = ToMultiText(fdoSense.Bibliography); lfSense.SenseRestrictions = ToMultiText(fdoSense.Restrictions); if (fdoSense.ReversalEntriesRC != null) { IEnumerable <string> reversalEntries = fdoSense.ReversalEntriesRC.Select(fdoReversalEntry => fdoReversalEntry.LongName); lfSense.ReversalEntries = LfStringArrayField.FromStrings(reversalEntries); } lfSense.ScientificName = LfMultiText.FromSingleITsString(fdoSense.ScientificName, ServiceLocator.WritingSystemFactory); lfSense.SemanticDomain = ToStringArrayField(SemDomListCode, fdoSense.SemanticDomainsRC); lfSense.SemanticsNote = ToMultiText(fdoSense.SemanticsNote); // fdoSense.SensesOS; // Not mapped because LF doesn't handle subsenses. TODO: When LF handles subsenses, map this one. lfSense.SenseType = ToStringField(SenseTypeListCode, fdoSense.SenseTypeRA); lfSense.SociolinguisticsNote = ToMultiText(fdoSense.SocioLinguisticsNote); if (fdoSense.Source != null) { lfSense.Source = LfMultiText.FromSingleITsString(fdoSense.Source, ServiceLocator.WritingSystemFactory); } lfSense.Status = ToStringArrayField(StatusListCode, fdoSense.StatusRA); lfSense.Usages = ToStringArrayField(UsageTypeListCode, fdoSense.UsageTypesRC); /* Fields not mapped because it doesn't make sense to map them (e.g., Hvo, backreferences, etc): * fdoSense.AllOwnedObjects; * fdoSense.AllSenses; * fdoSense.Cache; * fdoSense.CanDelete; * fdoSense.ChooserNameTS; * fdoSense.ClassID; * fdoSense.ClassName; * fdoSense.Entry; * fdoSense.EntryID; * fdoSense.FullReferenceName; * fdoSense.GetDesiredMsaType(); * fdoSense.Hvo; * fdoSense.ImportResidue; * fdoSense.IndexInOwner; * fdoSense.IsValidObject; * fdoSense.LexSenseReferences; * fdoSense.LongNameTSS; * fdoSense.ObjectIdName; * fdoSense.OwnedObjects; * fdoSense.Owner; * fdoSense.OwningFlid; * fdoSense.OwnOrd; * fdoSense.ReferringObjects; * fdoSense.ReversalNameForWs(wsVern); * fdoSense.SandboxMSA; // Set-only property * fdoSense.Self; * fdoSense.Services; * fdoSense.ShortName; * fdoSense.ShortNameTSS; * fdoSense.SortKey; * fdoSense.SortKey2; * fdoSense.SortKey2Alpha; * fdoSense.SortKeyWs; * fdoSense.VariantFormEntryBackRefs; * fdoSense.VisibleComplexFormBackRefs; */ /* Fields not mapped because LanguageForge doesn't handle that data: * fdoSense.AppendixesRC; * fdoSense.ComplexFormEntries; * fdoSense.ComplexFormsNotSubentries; * fdoSense.DoNotPublishInRC; * fdoSense.Subentries; * fdoSense.ThesaurusItemsRC; * fdoSense.LiftResidue; * fdoSense.LexSenseOutline; * fdoSense.PublishIn; */ BsonDocument customFieldsAndGuids = _convertCustomField.GetCustomFieldsForThisCmObject(fdoSense, "senses", ListConverters); BsonDocument customFieldsBson = customFieldsAndGuids["customFields"].AsBsonDocument; BsonDocument customFieldGuids = customFieldsAndGuids["customFieldGuids"].AsBsonDocument; // TODO: Role Views only set on initial clone if (LfProject.IsInitialClone) { ; } // If custom field was deleted in Flex, delete config here lfSense.CustomFields = customFieldsBson; lfSense.CustomFieldGuids = customFieldGuids; return(lfSense); }
/// <summary> /// Convert FDO lex entry to LF lex entry. /// </summary> /// <returns>LF entry /// <param name="fdoEntry">Fdo entry.</param> private LfLexEntry FdoLexEntryToLfLexEntry(ILexEntry fdoEntry) { if (fdoEntry == null) { return(null); } ILgWritingSystem AnalysisWritingSystem = ServiceLocator.LanguageProject.DefaultAnalysisWritingSystem; ILgWritingSystem VernacularWritingSystem = ServiceLocator.LanguageProject.DefaultVernacularWritingSystem; var lfEntry = new LfLexEntry(); IMoForm fdoLexeme = fdoEntry.LexemeFormOA; if (fdoLexeme == null) { lfEntry.Lexeme = null; } else { lfEntry.Lexeme = ToMultiText(fdoLexeme.Form); } // Other fields of fdoLexeme (AllomorphEnvironments, LiftResidue, MorphTypeRA, etc.) not mapped // Fields below in alphabetical order by ILexSense property, except for Lexeme foreach (IMoForm allomorph in fdoEntry.AlternateFormsOS) { // Do nothing; LanguageForge doesn't currently handle allomorphs, so we don't convert them } lfEntry.EntryBibliography = ToMultiText(fdoEntry.Bibliography); // TODO: Consider whether to use fdoEntry.CitationFormWithAffixType instead // (which would produce "-s" instead of "s" for the English plural suffix, for instance) lfEntry.CitationForm = ToMultiText(fdoEntry.CitationForm); lfEntry.Note = ToMultiText(fdoEntry.Comment); // DateModified and DateCreated can be confusing, because LF and FDO are doing two different // things with them. In FDO, there is just one DateModified and one DateCreated; simple. But // in LF, there is an AuthorInfo record as well, which contains its own ModifiedDate and CreatedDate // fields. (Note the word order: there's LfEntry.DateCreated, and LfEntry.AuthorInfo.CreatedDate). // The conversion we have chosen to use is: AuthorInfo will correspond to FDO. So FDO.DateCreated // becomes AuthorInfo.CreatedDate, and FDO.DateModified becomes AuthorInfo.ModifiedDate. The two // fields on the LF entry will instead refer to when the *Mongo record* was created or modified, // and the LfEntry.DateCreated and LfEntry.DateModified fields will never be put into FDO. var now = DateTime.UtcNow; if (LfProject.IsInitialClone) { lfEntry.DateCreated = now; } // LanguageForge needs this modified to know there is changed data lfEntry.DateModified = now; if (lfEntry.AuthorInfo == null) { lfEntry.AuthorInfo = new LfAuthorInfo(); } lfEntry.AuthorInfo.CreatedByUserRef = null; lfEntry.AuthorInfo.CreatedDate = fdoEntry.DateCreated.ToUniversalTime(); lfEntry.AuthorInfo.ModifiedByUserRef = null; lfEntry.AuthorInfo.ModifiedDate = fdoEntry.DateModified.ToUniversalTime(); #if DBVERSION_7000068 ILexEtymology fdoEtymology = fdoEntry.EtymologyOA; #else // TODO: Once LF's data model is updated from a single etymology to an array, // convert all of them instead of just the first. E.g., // foreach (ILexEtymology fdoEtymology in fdoEntry.EtymologyOS) { ... } ILexEtymology fdoEtymology = null; if (fdoEntry.EtymologyOS.Count > 0) { fdoEtymology = fdoEntry.EtymologyOS.First(); } #endif if (fdoEtymology != null) { lfEntry.Etymology = ToMultiText(fdoEtymology.Form); lfEntry.EtymologyComment = ToMultiText(fdoEtymology.Comment); lfEntry.EtymologyGloss = ToMultiText(fdoEtymology.Gloss); #if DBVERSION_7000068 lfEntry.EtymologySource = LfMultiText.FromSingleStringMapping(AnalysisWritingSystem.Id, fdoEtymology.Source); #else lfEntry.EtymologySource = ToMultiText(fdoEtymology.LanguageNotes); #endif // fdoEtymology.LiftResidue not mapped } lfEntry.Guid = fdoEntry.Guid; if (fdoEntry.LIFTid == null) { lfEntry.LiftId = null; } else { lfEntry.LiftId = fdoEntry.LIFTid.Normalize(System.Text.NormalizationForm.FormC); // Because LIFT files on disk are NFC and we need to make sure LiftIDs match those on disk } lfEntry.LiteralMeaning = ToMultiText(fdoEntry.LiteralMeaning); if (fdoEntry.PrimaryMorphType != null) { lfEntry.MorphologyType = fdoEntry.PrimaryMorphType.NameHierarchyString; } // TODO: Once LF's data model is updated from a single pronunciation to an array of pronunciations, convert all of them instead of just the first. E.g., //foreach (ILexPronunciation fdoPronunciation in fdoEntry.PronunciationsOS) { ... } if (fdoEntry.PronunciationsOS.Count > 0) { ILexPronunciation fdoPronunciation = fdoEntry.PronunciationsOS.First(); lfEntry.Pronunciation = ToMultiText(fdoPronunciation.Form); lfEntry.CvPattern = LfMultiText.FromSingleITsString(fdoPronunciation.CVPattern, ServiceLocator.WritingSystemFactory); lfEntry.Tone = LfMultiText.FromSingleITsString(fdoPronunciation.Tone, ServiceLocator.WritingSystemFactory); // TODO: Map fdoPronunciation.MediaFilesOS properly (converting video to sound files if necessary) lfEntry.Location = ToStringField(LocationListCode, fdoPronunciation.LocationRA); } lfEntry.EntryRestrictions = ToMultiText(fdoEntry.Restrictions); if (lfEntry.Senses == null) // Shouldn't happen, but let's be careful { lfEntry.Senses = new List <LfSense>(); } lfEntry.Senses.AddRange(fdoEntry.SensesOS.Select(FdoSenseToLfSense)); lfEntry.SummaryDefinition = ToMultiText(fdoEntry.SummaryDefinition); BsonDocument customFieldsAndGuids = _convertCustomField.GetCustomFieldsForThisCmObject(fdoEntry, "entry", ListConverters); BsonDocument customFieldsBson = customFieldsAndGuids["customFields"].AsBsonDocument; BsonDocument customFieldGuids = customFieldsAndGuids["customFieldGuids"].AsBsonDocument; lfEntry.CustomFields = customFieldsBson; lfEntry.CustomFieldGuids = customFieldGuids; return(lfEntry); /* Fields not mapped because it doesn't make sense to map them (e.g., Hvo, backreferences, etc): * fdoEntry.ComplexFormEntries; * fdoEntry.ComplexFormEntryRefs; * fdoEntry.ComplexFormsNotSubentries; * fdoEntry.EntryRefsOS; * fdoEntry.HasMoreThanOneSense; * fdoEntry.HeadWord; // Read-only virtual property * fdoEntry.IsMorphTypesMixed; // Read-only property * fdoEntry.LexEntryReferences; * fdoEntry.MainEntriesOrSensesRS; * fdoEntry.MinimalLexReferences; * fdoEntry.MorphoSyntaxAnalysesOC; * fdoEntry.MorphTypes; * fdoEntry.NumberOfSensesForEntry; * fdoEntry.PicturesOfSenses; * */ /* Fields that would make sense to map, but that we don't because LF doesn't handle them (e.g., allomorphs): * fdoEntry.AllAllomorphs; // LF doesn't handle allomorphs, so skip all allomorph-related fields * fdoEntry.AlternateFormsOS; * fdoEntry.CitationFormWithAffixType; // Citation form already mapped * fdoEntry.DoNotPublishInRC; * fdoEntry.DoNotShowMainEntryInRC; * fdoEntry.DoNotUseForParsing; * fdoEntry.HomographForm; * fdoEntry.HomographFormKey; * fdoEntry.HomographNumber; * fdoEntry.ImportResidue; * fdoEntry.LiftResidue; * fdoEntry.PronunciationsOS * fdoEntry.PublishAsMinorEntry; * fdoEntry.PublishIn; * fdoEntry.ShowMainEntryIn; * fdoEntry.Subentries; * fdoEntry.VariantEntryRefs; * fdoEntry.VariantFormEntries; * fdoEntry.VisibleComplexFormBackRefs; * fdoEntry.VisibleComplexFormEntries; * fdoEntry.VisibleVariantEntryRefs; * */ }
/// <summary> /// Gets the data for one custom field, and any relevant GUIDs. /// </summary> /// <param name="hvo">Hvo of object we're getting the field for.</param> /// <param name="flid">Flid for this field.</param> /// <param name="fieldSourceType">Either "entry", "senses" or "examples". Could also be "allomorphs", eventually.</param> /// <param name="bsonForThisField">Output of a BsonDocument with the following structure: <br /> /// { fieldName: { "value": BsonValue, "guid": "some-guid-as-a-string" } } <br /> /// -OR- <br /> /// { fieldName: { "value": BsonValue, "guid": ["guid1", "guid2", "guid3"] } } <br /> /// The format of the fieldName key will be "customField_FOO_field_name_with_underscores", /// where FOO is one of "entry", "senses", or "examples". <br /> /// The type of the "guid" value (array or string) will determine whether there is a single GUID, /// or a list of GUIDs that happens to contain only one entry. /// If there is no "guid" key, that field has no need for a GUID. (E.g., a number). /// </param> /// <param name="listConverters">Dictionary of ConvertLcmToMongoOptionList instances, keyed by list code</param> private BsonDocument GetCustomFieldData(int hvo, int flid, string fieldSourceType, IDictionary <string, ConvertLcmToMongoOptionList> listConverters) { BsonValue fieldValue = null; BsonValue fieldGuid = null; // Might be a single value, might be a list (as a BsonArray) ISilDataAccessManaged data = (ISilDataAccessManaged)cache.DomainDataByFlid; CellarPropertyType LcmFieldType = (CellarPropertyType)LcmMetaData.GetFieldType(flid); var dataGuids = new List <Guid>(); // Valid field types in Lcm are GenDate, Integer, String, OwningAtomic, ReferenceAtomic, and ReferenceCollection, so that's all we implement. switch (LcmFieldType) { case CellarPropertyType.GenDate: GenDate genDate = data.get_GenDateProp(hvo, flid); string genDateStr = genDate.ToLongString(); // LF wants single-string fields in the format { "ws": { "value": "contents" } } fieldValue = String.IsNullOrEmpty(genDateStr) ? null : LfMultiText.FromSingleStringMapping( MagicStrings.LanguageCodeForGenDateFields, genDateStr).AsBsonDocument(); break; // When parsing, will use GenDate.TryParse(str, out genDate) case CellarPropertyType.Integer: fieldValue = new BsonInt32(data.get_IntProp(hvo, flid)); if (fieldValue.AsInt32 == default(Int32)) { fieldValue = null; // Suppress int fields with 0 in them, to save Mongo DB space } else { // LF wants single-string fields in the format { "ws": { "value": "contents" } } fieldValue = LfMultiText.FromSingleStringMapping( MagicStrings.LanguageCodeForIntFields, fieldValue.AsInt32.ToString()).AsBsonDocument(); } break; case CellarPropertyType.OwningAtomic: case CellarPropertyType.ReferenceAtomic: int ownedHvo = data.get_ObjectProp(hvo, flid); fieldValue = GetCustomReferencedObject(ownedHvo, flid, listConverters, ref dataGuids); if (fieldValue != null && LcmFieldType == CellarPropertyType.ReferenceAtomic) { // Single CmPossiblity reference - LF expects format like { "value": "key of possibility" } fieldValue = new BsonDocument("value", fieldValue); } fieldGuid = new BsonString(dataGuids.FirstOrDefault().ToString()); break; case CellarPropertyType.MultiUnicode: ITsMultiString tss = data.get_MultiStringProp(hvo, flid); if (tss != null && tss.StringCount > 0) { fieldValue = LfMultiText.FromMultiITsString(tss, servLoc.WritingSystemManager).AsBsonDocument(); } break; case CellarPropertyType.OwningCollection: case CellarPropertyType.OwningSequence: case CellarPropertyType.ReferenceCollection: case CellarPropertyType.ReferenceSequence: int[] listHvos = data.VecProp(hvo, flid); var innerValues = new BsonArray(listHvos.Select(listHvo => GetCustomReferencedObject(listHvo, flid, listConverters, ref dataGuids)).Where(x => x != null)); if (innerValues.Count == 0) { fieldValue = null; } else { fieldValue = new BsonDocument("values", innerValues); fieldGuid = new BsonArray(dataGuids.Select(guid => guid.ToString())); } break; case CellarPropertyType.String: ITsString iTsValue = data.get_StringProp(hvo, flid); if (iTsValue == null || String.IsNullOrEmpty(iTsValue.Text)) { fieldValue = null; } else { fieldValue = LfMultiText.FromSingleITsString(iTsValue, servLoc.WritingSystemManager).AsBsonDocument(); } break; default: fieldValue = null; if (logger != null) { logger.Warning("Lcm CellarPropertyType.{0} not recognized for LF custom field", LcmFieldType.ToString()); } break; } if (fieldValue == null) { return(null); } else { var result = new BsonDocument(); result.Add("value", fieldValue ?? BsonNull.Value); // BsonValues aren't allowed to have C# nulls; they have their own null representation if (fieldGuid is BsonArray) { result.Add("guid", fieldGuid, ((BsonArray)fieldGuid).Count > 0); } else { result.Add("guid", fieldGuid, fieldGuid != null); } return(result); } }
public void Action_ChangedWithSampleData_ShouldUpdatePictures() { // Setup initial Mongo project has 1 picture and 2 captions var lfProj = _lfProj; var data = new SampleData(); _conn.UpdateMockLfLexEntry(data.bsonTestData); string expectedInternalFileName = Path.Combine("Pictures", data.bsonTestData["senses"][0]["pictures"][0]["fileName"].ToString()); string expectedExternalFileName = data.bsonTestData["senses"][0]["pictures"][1]["fileName"].ToString(); int newMongoPictureCount = data.bsonTestData["senses"][0]["pictures"].AsBsonArray.Count; int newMongoCaptionCount = data.bsonTestData["senses"][0]["pictures"][0]["caption"].AsBsonDocument.Count(); Assert.That(newMongoPictureCount, Is.EqualTo(2)); Assert.That(newMongoCaptionCount, Is.EqualTo(2)); // Initial FDO project has 63 entries, 3 internal pictures, and 1 externally linked picture FdoCache cache = _cache; ILexEntryRepository entryRepo = _servLoc.GetInstance <ILexEntryRepository>(); int originalNumOfFdoPictures = entryRepo.AllInstances(). Count(e => (e.SensesOS.Count > 0) && (e.SensesOS[0].PicturesOS.Count > 0)); Assert.That(entryRepo.Count, Is.EqualTo(OriginalNumOfFdoEntries)); Assert.That(originalNumOfFdoPictures, Is.EqualTo(3 + 1)); string expectedGuidStr = data.bsonTestData["guid"].AsString; Guid expectedGuid = Guid.Parse(expectedGuidStr); var entryBefore = cache.ServiceLocator.GetObject(expectedGuid) as ILexEntry; Assert.That(entryBefore.SensesOS.Count, Is.GreaterThan(0)); Assert.That(entryBefore.SensesOS.First().PicturesOS.Count, Is.EqualTo(1)); // Exercise adding 1 picture with 2 captions. Note that the picture that was previously attached // to this FDO entry will end up being deleted, because it does not have a corresponding picture in LF. data.bsonTestData["authorInfo"]["modifiedDate"] = DateTime.UtcNow; _conn.UpdateMockLfLexEntry(data.bsonTestData); sutMongoToFdo.Run(lfProj); // Verify "Added" picture is now the only picture on the sense (because the "old" picture was deleted), // and that it has 2 captions with the expected values. entryRepo = _servLoc.GetInstance <ILexEntryRepository>(); int numOfFdoPictures = entryRepo.AllInstances(). Count(e => (e.SensesOS.Count > 0) && (e.SensesOS[0].PicturesOS.Count > 0)); Assert.That(entryRepo.Count, Is.EqualTo(OriginalNumOfFdoEntries)); Assert.That(numOfFdoPictures, Is.EqualTo(originalNumOfFdoPictures)); var entry = cache.ServiceLocator.GetObject(expectedGuid) as ILexEntry; Assert.IsNotNull(entry); Assert.That(entry.Guid, Is.EqualTo(expectedGuid)); Assert.That(entry.SensesOS.Count, Is.GreaterThan(0)); Assert.That(entry.SensesOS.First().PicturesOS.Count, Is.EqualTo(2)); Assert.That(entry.SensesOS[0].PicturesOS[0].PictureFileRA.InternalPath.ToString(), Is.EqualTo(expectedInternalFileName)); Assert.That(entry.SensesOS[0].PicturesOS[1].PictureFileRA.InternalPath.ToString(), Is.EqualTo(expectedExternalFileName)); LfMultiText expectedNewCaption = ConvertFdoToMongoLexicon. ToMultiText(entry.SensesOS[0].PicturesOS[0].Caption, cache.ServiceLocator.WritingSystemManager); int expectedNumOfNewCaptions = expectedNewCaption.Count(); Assert.That(expectedNumOfNewCaptions, Is.EqualTo(2)); string expectedNewVernacularCaption = expectedNewCaption["qaa-x-kal"].Value; string expectedNewAnalysisCaption = expectedNewCaption["en"].Value; Assert.That(expectedNewVernacularCaption.Equals("First Vernacular caption")); Assert.That(expectedNewAnalysisCaption.Equals("Internal path reference")); var testSubEntry = cache.ServiceLocator.GetObject(Guid.Parse(TestSubEntryGuidStr)) as ILexEntry; Assert.That(testSubEntry, Is.Not.Null); Assert.That(testSubEntry.SensesOS[0].PicturesOS[0].PictureFileRA.InternalPath.ToString(), Is.EqualTo("Pictures\\TestImage.tif")); var kenEntry = cache.ServiceLocator.GetObject(Guid.Parse(KenEntryGuidStr)) as ILexEntry; Assert.That(kenEntry, Is.Not.Null); Assert.That(kenEntry.SensesOS[0].PicturesOS[0].PictureFileRA.InternalPath.ToString(), Is.EqualTo("F:\\src\\xForge\\web-languageforge\\test\\php\\common\\TestImage.jpg")); }
// Used in subclasses to help reduce size of MongoDB JSON serializations protected bool _ShouldSerializeLfMultiText(LfMultiText value) { return value != null && !value.IsEmpty; }