Skip to content

Commit

Permalink
Allow read/write of project settings in C# (#857)
Browse files Browse the repository at this point in the history
  • Loading branch information
lyonsil authored Apr 18, 2024
1 parent bbff975 commit d019456
Show file tree
Hide file tree
Showing 8 changed files with 280 additions and 29 deletions.
1 change: 0 additions & 1 deletion c-sharp/JsonUtils/VerseRefConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ out string errorMessage
catch (Exception e)
{
verseRef = new VerseRef();
Console.Error.Write(e.ToString());
errorMessage = $"Invalid VerseRef ({jsonString}): {e.Message}";
return false;
}
Expand Down
62 changes: 56 additions & 6 deletions c-sharp/Projects/ParatextProjectDataProvider.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Text.Json.Nodes;
using Newtonsoft.Json.Linq;
using Paranext.DataProvider.MessageHandlers;
using Paranext.DataProvider.MessageTransports;

Expand All @@ -23,13 +25,33 @@ ProjectDetails projectDetails

Getters.Add("getChapterUSX", GetChapterUSX);
Setters.Add("setChapterUSX", SetChapterUSX);

Getters.Add("getSetting", GetProjectSetting);
Setters.Add("setSetting", SetProjectSetting);
}

protected override Task StartDataProvider()
{
return Task.CompletedTask;
}

protected override ResponseToRequest HandleRequest(string functionName, JsonArray args)
{
try
{
return functionName switch
{
"resetSetting" => ResetProjectSetting(args[0]!.ToJsonString()),
_ => base.HandleRequest(functionName, args)
};
}
catch (Exception e)
{
Console.Error.WriteLine(e.ToString());
return ResponseToRequest.Failed(e.ToString());
}
}

protected override string GetProjectStorageInterpreterId()
{
return _paratextPsi.DataProviderName;
Expand All @@ -45,6 +67,34 @@ protected override ResponseToRequest SetExtensionData(ProjectDataScope dataScope
return _paratextPsi.SetExtensionData(dataScope, data);
}

private ResponseToRequest GetProjectSetting(string key)
{
return Get(ProjectDataType.SETTINGS, JToken.Parse(key).ToString());
}

private ResponseToRequest SetProjectSetting(string key, string value)
{
return Set(ProjectDataType.SETTINGS, JToken.Parse(key).ToString(), value);
}

// Typically for "reset" we would want to erase the setting and then call "getDefault" if a
// setting is not present when "get" is called. Since we're using PT settings as the backing
// store here, though, we want to keep all properties filled in inside of Settings.xml files
private ResponseToRequest ResetProjectSetting(string key)
{
string settingName = JToken.Parse(key).ToString();
string? defaultValue = ProjectSettingsService.GetDefault(
PapiClient,
settingName,
ProjectType.Paratext
);
if (defaultValue == null)
return ResponseToRequest.Failed($"Default value for {settingName} was null");
ResponseToRequest retVal = Set(ProjectDataType.SETTINGS, settingName, defaultValue);
SendDataUpdateEvent(retVal.Contents);
return retVal;
}

private ResponseToRequest Get(string dataType, string dataQualifier)
{
ProjectDataScope scope =
Expand Down Expand Up @@ -72,34 +122,34 @@ private ResponseToRequest Set(string dataType, string dataQualifier, string data
#region USFM handling methods
private ResponseToRequest GetBookUSFM(string jsonString)
{
return Get(ParatextProjectStorageInterpreter.BookUSFM, jsonString);
return Get(ProjectDataType.BOOK_USFM, jsonString);
}

private ResponseToRequest GetChapterUSFM(string jsonString)
{
return Get(ParatextProjectStorageInterpreter.ChapterUSFM, jsonString);
return Get(ProjectDataType.CHAPTER_USFM, jsonString);
}

private ResponseToRequest GetVerseUSFM(string jsonString)
{
return Get(ParatextProjectStorageInterpreter.VerseUSFM, jsonString);
return Get(ProjectDataType.VERSE_USFM, jsonString);
}

private ResponseToRequest SetChapterUSFM(string dataQualifier, string data)
{
return Set(ParatextProjectStorageInterpreter.ChapterUSFM, dataQualifier, data);
return Set(ProjectDataType.CHAPTER_USFM, dataQualifier, data);
}
#endregion

#region USX handling methods
private ResponseToRequest GetChapterUSX(string jsonString)
{
return Get(ParatextProjectStorageInterpreter.ChapterUSX, jsonString);
return Get(ProjectDataType.CHAPTER_USX, jsonString);
}

private ResponseToRequest SetChapterUSX(string dataQualifier, string data)
{
return Set(ParatextProjectStorageInterpreter.ChapterUSX, dataQualifier, data);
return Set(ProjectDataType.CHAPTER_USX, dataQualifier, data);
}
#endregion
}
83 changes: 63 additions & 20 deletions c-sharp/Projects/ParatextProjectStorageInterpreter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,15 @@ namespace Paranext.DataProvider.Projects;
internal class ParatextProjectStorageInterpreter : ProjectStorageInterpreter
{
#region Constants / Member variables
public const string BookUSFM = "BookUSFM";
public const string ChapterUSFM = "ChapterUSFM";
public const string VerseUSFM = "VerseUSFM";
public const string ChapterUSX = "ChapterUSX";

// All data types related to Scripture editing. Changes to any portion of Scripture should send
// out updates to all these data types
public static readonly List<string> AllScriptureDataTypes = new List<string>
{
BookUSFM,
ChapterUSFM,
VerseUSFM,
ChapterUSX
};
public static readonly List<string> AllScriptureDataTypes =
[
ProjectDataType.BOOK_USFM,
ProjectDataType.CHAPTER_USFM,
ProjectDataType.VERSE_USFM,
ProjectDataType.CHAPTER_USX
];

private readonly LocalParatextProjects _paratextProjects;
#endregion
Expand Down Expand Up @@ -140,22 +135,25 @@ public override ResponseToRequest GetProjectData(ProjectDataScope scope)

return scope.DataType switch
{
BookUSFM
ProjectDataType.BOOK_USFM
=> string.IsNullOrEmpty(error)
? ResponseToRequest.Succeeded(scrText.GetText(verseRef, false, true))
: ResponseToRequest.Failed(error),
ChapterUSFM
ProjectDataType.CHAPTER_USFM
=> string.IsNullOrEmpty(error)
? ResponseToRequest.Succeeded(scrText.GetText(verseRef, true, true))
: ResponseToRequest.Failed(error),
VerseUSFM
ProjectDataType.VERSE_USFM
=> string.IsNullOrEmpty(error)
? ResponseToRequest.Succeeded(scrText.Parser.GetVerseUsfmText(verseRef))
: ResponseToRequest.Failed(error),
ChapterUSX
ProjectDataType.CHAPTER_USX
=> string.IsNullOrEmpty(error)
? ResponseToRequest.Succeeded(GetChapterUsx(scrText, verseRef))
: ResponseToRequest.Failed(error),
ProjectDataType.SETTINGS
=> ResponseToRequest.Succeeded(scrText.Settings.ParametersDictionary[
ProjectSettings.GetParatextSettingNameFromPlatformBibleSettingName(scope.DataQualifier) ?? scope.DataQualifier]),
_ => ResponseToRequest.Failed($"Unknown data type: {scope.DataType}")
};
}
Expand Down Expand Up @@ -184,7 +182,7 @@ public override ResponseToRequest SetProjectData(ProjectDataScope scope, string

switch (scope.DataType)
{
case ChapterUSFM:
case ProjectDataType.CHAPTER_USFM:
if (!string.IsNullOrEmpty(error))
return ResponseToRequest.Failed(error);
RunWithinLock(
Expand All @@ -200,9 +198,9 @@ public override ResponseToRequest SetProjectData(ProjectDataScope scope, string
);
}
);
// The value of returned strings are case sensitive and cannot change unless data provider subscriptions change
// The value of returned strings are case-sensitive and cannot change unless data provider subscriptions change
return ResponseToRequest.Succeeded(AllScriptureDataTypes);
case ChapterUSX:
case ProjectDataType.CHAPTER_USX:
if (!string.IsNullOrEmpty(error))
return ResponseToRequest.Failed(error);
ResponseToRequest? response = null;
Expand All @@ -211,6 +209,51 @@ public override ResponseToRequest SetProjectData(ProjectDataScope scope, string
writeLock => response = SetChapterUsx(scrText, verseRef, data, writeLock)
);
return response ?? ResponseToRequest.Failed("Unknown error occurred");
case ProjectDataType.SETTINGS:
// If there is no Paratext setting for the name given, we'll create one lower down
ResponseToRequest? currentValueResponse = ResponseToRequest.Failed("");
try
{
currentValueResponse = GetProjectData(scope);
}
catch (KeyNotFoundException) {}

// Make sure the value we're planning to set is valid
var currentValueJson = currentValueResponse.Success
? JsonConvert.SerializeObject(currentValueResponse.Contents)
: "";
if (!ProjectSettingsService.IsValid(
PapiClient,
data,
currentValueJson,
scope.DataQualifier,
"",
ProjectType.Paratext))
return ResponseToRequest.Failed($"Validation failed for {scope.DataQualifier}");

// Figure out which setting name to use
var paratextSettingName =
ProjectSettings.GetParatextSettingNameFromPlatformBibleSettingName(
scope.DataQualifier) ?? scope.DataQualifier;

// Now actually write the setting
string? errorMessage = null;
RunWithinLock(
WriteScope.AllSettingsFiles(),
_ => {
try
{
scrText.Settings.SetSetting(paratextSettingName, data);
scrText.Settings.Save();
}
catch (Exception ex)
{
errorMessage = ex.Message;
}
});
return (errorMessage != null)
? ResponseToRequest.Failed(errorMessage)
: ResponseToRequest.Succeeded(ProjectDataType.SETTINGS);
default:
return ResponseToRequest.Failed($"Unknown data type: {scope.DataType}");
}
Expand Down Expand Up @@ -301,7 +344,7 @@ public override ResponseToRequest SetExtensionData(ProjectDataScope scope, strin
// ReSharper restore AccessToDisposedClosure
}
);
// The value of returned string is case sensitive and cannot change unless data provider subscriptions change
// The value of returned string is case-sensitive and cannot change unless data provider subscriptions change
return ResponseToRequest.Succeeded("ExtensionData");
}
catch (Exception e)
Expand Down
10 changes: 10 additions & 0 deletions c-sharp/Projects/ProjectDataType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Paranext.DataProvider.Projects;

public sealed class ProjectDataType
{
public const string BOOK_USFM = "BookUSFM";
public const string CHAPTER_USFM = "ChapterUSFM";
public const string CHAPTER_USX = "ChapterUSX";
public const string SETTINGS = "Settings";
public const string VERSE_USFM = "VerseUSFM";
}
53 changes: 53 additions & 0 deletions c-sharp/Projects/ProjectSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
namespace Paranext.DataProvider.Projects;

public sealed class ProjectSettings
{
public const string PB_BOOKS_PRESENT = "platformScripture.booksPresent";
public const string PT_BOOKS_PRESENT = "BooksPresent";

public const string PB_FULL_NAME = "platform.fullName";
public const string PT_FULL_NAME = "FullName";

public const string PB_LANGUAGE = "platform.language";
public const string PT_LANGUAGE = "Language";

public const string PB_VERSIFICATION = "platformScripture.versification";
public const string PT_VERSIFICATION = "Versification";

// Make sure this dictionary gets updated whenever new settings are added
private static readonly Dictionary<string, string> s_platformBibleToParatextSettingsNames =
new()
{
{ PB_BOOKS_PRESENT, PT_BOOKS_PRESENT },
{ PB_FULL_NAME, PT_FULL_NAME },
{ PB_LANGUAGE, PT_LANGUAGE },
{ PB_VERSIFICATION, PT_VERSIFICATION },
};

private static readonly Dictionary<string, string> s_paratextToPlatformBibleSettingsNames =
s_platformBibleToParatextSettingsNames.ToDictionary((i) => i.Value, (i) => i.Key);

/// <summary>
/// Convert project setting names from Platform.Bible terminology to Paratext terminology
/// </summary>
/// <param name="pbSettingName">Setting name in Platform.Bible terminology</param>
/// <returns>Setting name in Paratext terminology if a mapping exists</returns>
public static string? GetParatextSettingNameFromPlatformBibleSettingName(string pbSettingName)
{
return s_platformBibleToParatextSettingsNames.TryGetValue(pbSettingName, out string? retVal)
? retVal
: null;
}

/// <summary>
/// Convert project setting names from Paratext terminology to Platform.Bible terminology
/// </summary>
/// <param name="ptSettingName">Setting name in Paratext terminology</param>
/// <returns>Setting name in Platform.Bible terminology if a mapping exists</returns>
public static string? GetPlatformBibleSettingNameFromParatextSettingName(string ptSettingName)
{
return s_paratextToPlatformBibleSettingsNames.TryGetValue(ptSettingName, out string? retVal)
? retVal
: null;
}
}
Loading

0 comments on commit d019456

Please sign in to comment.