Skip to content
Draft
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
54b5fc4
Added information about projects and events to track creating and ope…
tombogle Dec 12, 2025
da7147c
Merge branch 'main' into analytics-tracking-enhancements
tombogle Dec 18, 2025
393b97b
Added an event to note details of significant progress.
tombogle Jan 6, 2026
bbdb5e1
Updated copyright to 2026
tombogle Jan 6, 2026
5768024
Fixed problems when stats are not fully initialized
tombogle Jan 6, 2026
edbca9f
Upgraded SIL.DesktopAnalytics
tombogle Jan 12, 2026
b3a4bf2
Merge branch 'main' into analytics-tracking-enhancements
tombogle Jan 12, 2026
016fab7
Unified on System.Text.Json 9.0.0 to prevent runtime failure:
tombogle Jan 13, 2026
c71c5fd
Improved installer to avoid picking up older DLL versions referenced …
tombogle Jan 13, 2026
ff5e863
Revert errant installer change
tombogle Jan 13, 2026
ba01181
Used locking to prevent race conditions in statistics reporting.
tombogle Jan 13, 2026
ede6694
Cleaned up some long-standing compiler warnings
tombogle Feb 26, 2026
7563c3b
Upgrade libpalaso SIL DLLs and L10nSharp to latest betas.
tombogle May 26, 2026
58a232f
Add ApplicationContainer.SayMoreLocalizationManager static property
tombogle Jun 5, 2026
04fc998
Replace lm-parameter EditorBase constructors with static-based no-arg…
tombogle Jun 5, 2026
f105fc4
Make ComponentFileGrid constructor no-arg, use SayMoreLocalizationMan…
tombogle Jun 8, 2026
5cbfb09
StatisticsView/ProgressScreen: remove ILocalizationManager param, use…
tombogle Jun 8, 2026
0df26dd
Fix indentation on null-conditional UiLanguageChanged guards in Stati…
tombogle Jun 8, 2026
104088d
Task 5: remove ILocalizationManager from FileType hierarchy
tombogle Jun 8, 2026
05e6a4d
Task 6: remove lm guard from HandleStringsLocalized in 7 EditorBase s…
tombogle Jun 8, 2026
870e9f7
Task 7: fix PersonBasicEditor — remove no-arg HandleStringsLocalized …
tombogle Jun 8, 2026
72ff644
Task 8: drop lm constructor param and clean up HandleStringsLocalized…
tombogle Jun 8, 2026
3a7dcdc
Task 9: remove ILocalizationManager from ProjectMetadataScreen and Pr…
tombogle Jun 8, 2026
cc5cb1d
Task 10: rewrite UILanguageDlg — remove ILocalizationManager, replace…
tombogle Jun 8, 2026
c3e4e92
Task 11a: fix two missed EditorBase subclasses (BasicFieldGridEditor,…
tombogle Jun 8, 2026
cb3ef52
Refactor: move HandleStringsLocalized initial call from EditorBase co…
tombogle Jun 9, 2026
39df720
Add superpowers/ to .gitignore
tombogle Jun 9, 2026
4d135fd
Cleanup: remove dead tabText constructor param and Initialize method …
tombogle Jun 9, 2026
928ad45
Fix: defer gender ComboBox selection until items are populated (crash…
tombogle Jun 10, 2026
a5d78b4
Merge branch 'main' into analytics-tracking-enhancements
tombogle Jun 25, 2026
66fa26d
Merge branch 'main' into analytics-tracking-enhancements
tombogle Jun 25, 2026
bc0d247
Fixed problem with unit tests when trying to select an invalid index …
tombogle Jun 25, 2026
1aa9d31
Updated to latest (beta) SIL DLLs
tombogle Jul 1, 2026
64ca462
Fixed CI build issues
tombogle Aug 13, 2026
f64f277
Replace hand-rolled installer analytics UI with SIL.Installer package
tombogle Aug 13, 2026
3eb7be6
Bump dorny/test-reporter to v3 to clear the Node.js 20 deprecation wa…
tombogle Aug 14, 2026
bb03ff6
Fix CI version computation: wire Version into env, auto-assign PR bui…
tombogle Aug 14, 2026
93e291c
Added missing file to installer
tombogle Aug 14, 2026
7ac5240
Moved instantiation of AnalyticsConsent out of release-only code bloc…
tombogle Aug 17, 2026
379f48b
Dealt with PR review issues:
tombogle Aug 18, 2026
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
2 changes: 1 addition & 1 deletion DistFiles/aboutBox.htm
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<base target="_blank" rel="noopener noreferrer"><!-- Links should open in new window -->
</head>
<body>
<h1 style="margin-top:8px">Copyright © 2011-2025 <a href="https://www.sil.org/">SIL Global</a></h1>
<h1 style="margin-top:8px">Copyright © 2011-2026 <a href="https://www.sil.org/">SIL Global</a></h1>

<h1>License</h1>
<p>Open source (<a href="https://sil.mit-license.org/">MIT</a>).</p>
Expand Down
2 changes: 2 additions & 0 deletions SayMore.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HTML/@EntryIndexedValue">HTML</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IE/@EntryIndexedValue">IE</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IMDI/@EntryIndexedValue">IMDI</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ISO/@EntryIndexedValue">ISO</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MRU/@EntryIndexedValue">MRU</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=OK/@EntryIndexedValue">OK</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RAMP/@EntryIndexedValue">RAMP</s:String>
Expand Down
2 changes: 1 addition & 1 deletion build/SayMore.proj
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<LocalPackagesRoot Condition="'$(LocalPackagesRoot)'==''">$(RootDir)/packages/</LocalPackagesRoot>
<PalasoL10nsVersion>16.2.0</PalasoL10nsVersion>
<PalasoL10nsRootDir>$(NuGetPackageRoot)SIL.libpalaso.l10ns/$(PalasoL10nsVersion)/</PalasoL10nsRootDir>
<SILReleaseTasksProps>$(NuGetPackageRoot)SIL.ReleaseTasks/3.1.1/build/SIL.ReleaseTasks.props</SILReleaseTasksProps>
<SILReleaseTasksProps>$(NuGetPackageRoot)SIL.ReleaseTasks/3.2.0/build/SIL.ReleaseTasks.props</SILReleaseTasksProps>
<BuildTasksVersionFolder Condition="'$(BuildTasksVersionFolder)' == ''">$(LocalPackagesRoot)SIL.BuildTasks/</BuildTasksVersionFolder>
<BuildTasksDll>$(BuildTasksVersionFolder)tools/SIL.BuildTasks.dll</BuildTasksDll>
<SILBuildTasksProps>$(BuildTasksVersionFolder)build/SIL.BuildTasks.props</SILBuildTasksProps>
Expand Down
2 changes: 1 addition & 1 deletion build/TestInstallerBuild.bat
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ GOTO pauseforusertoseeoutput
REM :unexpectedsystemvariableinuse
REM @ECHO Unexpected system variable msbuildpath is in use. Value: %msbuildpath%
:pauseforusertoseeoutput
ECHO %CMDCMDLINE% | findstr /i "/c" >nul
ECHO %CMDCMDLINE% | findstr /i /c:"/c" >nul
IF NOT errorlevel 1 PAUSE
18 changes: 12 additions & 6 deletions src/Installer/Installer.wxs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<?define Property_UpgradeCode = "{CBCDEE93-B8EF-445e-98A9-B8CAF9502F0D}" ?>

<?define OutputDir = "..\..\output\x64\$(var.Configuration)\net48\" ?>
<?define NativeAssetsDir = "$(var.OutputDir)lib\win-x64\" ?>

<!-- good intro to the component vs. file thing, and why each file here is a separate component:
http://blogs.msdn.com/robmen/archive/2003/10/04/56479.aspx -->
Expand Down Expand Up @@ -200,6 +201,10 @@ are trying to support, you're better off using non-advertised shortcuts. "-->
<File Id="MediaInfo.dll" Name="MediaInfo.dll" KeyPath="yes" Source="$(var.OutputDir)MediaInfo.dll"/>
</Component>

<Component Id="MediaInfoDLL.dll" Guid="{370327F7-D3D9-4673-87C6-6EFE3ED7DB8E}" Win64="yes">
<File Id="MediaInfoDLL.dll" Name="MediaInfoDLL.dll" KeyPath="yes" Source="$(var.OutputDir)MediaInfoDLL.dll"/>
</Component>

<Component Id="NetSparkle.Net40.dll" Guid="{36870B13-214E-4F3E-8226-2FE689C7EEDE}" Win64="yes">
<File Id="NetSparkle.Net40.dll" Name="NetSparkle.Net40.dll" KeyPath="yes" Source="$(var.OutputDir)NetSparkle.Net40.dll"/>
</Component>
Expand Down Expand Up @@ -341,22 +346,22 @@ are trying to support, you're better off using non-advertised shortcuts. "-->
</Component>

<Component Id="icudt59.x64" Guid="{F0CAECD1-8D7E-4D4F-9147-A4E8ED2B741B}" Win64="yes">
<File Id="icudt59.x64" Name="icudt59.dll" KeyPath="yes" Source="$(var.OutputDir)lib\win-x64\icudt59.dll" />
<File Id="icudt59.x64" Name="icudt59.dll" KeyPath="yes" Source="$(var.NativeAssetsDir)icudt59.dll" />
</Component>
<Component Id="icuin59.x64" Guid="{F0CAECD1-8D7E-4D4F-9147-A4E8ED2B741C}" Win64="yes">
<File Id="icuin59.x64" Name="icuin59.dll" KeyPath="yes" Source="$(var.OutputDir)lib\win-x64\icuin59.dll" />
<File Id="icuin59.x64" Name="icuin59.dll" KeyPath="yes" Source="$(var.NativeAssetsDir)icuin59.dll" />
</Component>
<Component Id="icuuc59.x64" Guid="{F0CAECD1-8D7E-4D4F-9147-A4E8ED2B741D}" Win64="yes">
<File Id="icuuc59.x64" Name="icuuc59.dll" KeyPath="yes" Source="$(var.OutputDir)lib\win-x64\icuuc59.dll" />
<File Id="icuuc59.x64" Name="icuuc59.dll" KeyPath="yes" Source="$(var.NativeAssetsDir)icuuc59.dll" />
</Component>
<Component Id="ikpFlac.x64" Guid="{F0CAECD1-8D7E-4D4F-9147-A4E8ED2B741E}" Win64="yes">
<File Id="ikpFlac.x64" Name="ikpFlac.dll" KeyPath="yes" Source="$(var.OutputDir)lib\win-x64\ikpFlac.dll" />
<File Id="ikpFlac.x64" Name="ikpFlac.dll" KeyPath="yes" Source="$(var.NativeAssetsDir)ikpFlac.dll" />
</Component>
<Component Id="ikpMP3.x64" Guid="{F0CAECD1-8D7E-4D4F-9147-A4E8ED2B742A}" Win64="yes">
<File Id="ikpMP3.x64" Name="ikpMP3.dll" KeyPath="yes" Source="$(var.OutputDir)lib\win-x64\ikpMP3.dll" />
<File Id="ikpMP3.x64" Name="ikpMP3.dll" KeyPath="yes" Source="$(var.NativeAssetsDir)ikpMP3.dll" />
</Component>
<Component Id="irrKlang.NET4.x64" Guid="{F0CAECD1-8D7E-4D4F-9147-A4E8ED2B742B}" Win64="yes">
<File Id="irrKlang.NET4.x64" Name="irrKlang.NET4.dll" KeyPath="yes" Source="$(var.OutputDir)lib\win-x64\irrKlang.NET4.dll" />
<File Id="irrKlang.NET4.x64" Name="irrKlang.NET4.dll" KeyPath="yes" Source="$(var.NativeAssetsDir)irrKlang.NET4.dll" />
</Component>
</Directory>
</Directory>
Expand All @@ -378,6 +383,7 @@ are trying to support, you're better off using non-advertised shortcuts. "-->
<ComponentRef Id="L10NSharp.dll"/>
<ComponentRef Id="Markdig.Signed.dll"/>
<ComponentRef Id="MediaInfo.dll"/>
<ComponentRef Id="MediaInfoDLL.dll"/>
<ComponentRef Id="NetSparkle.Net40.dll"/>
<ComponentRef Id="Microsoft.Bcl.AsyncInterfaces.dll"/>
<ComponentRef Id="Microsoft.Extensions.DependencyModel.dll"/>
Expand Down
84 changes: 31 additions & 53 deletions src/SayMore/Model/Files/DataGathering/BackgroundFileProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,6 @@

namespace SayMore.Model.Files.DataGathering
{
/// ----------------------------------------------------------------------------------------
public interface ISingleListDataGatherer
{
event EventHandler NewDataAvailable;
event EventHandler FinishedProcessingAllFiles;
IEnumerable<string> GetValues();
}

/// ----------------------------------------------------------------------------------------
/// <summary>
/// Gives lists of data, indexed by a key into a dictionary
Expand All @@ -27,15 +19,14 @@ public interface ISingleListDataGatherer
public interface IMultiListDataProvider
{
event EventHandler NewDataAvailable;
event EventHandler FinishedProcessingAllFiles;
Dictionary<string, IEnumerable<string>> GetValueLists(bool includeUnattestedFactoryChoices);
}

/// ----------------------------------------------------------------------------------------
/// <summary>
/// This is the base class for processes which live in the background,
/// gathering data about the files in the collection so that this data
/// is quickly accesible when needed.
/// is quickly accessible when needed.
/// </summary>
/// ----------------------------------------------------------------------------------------
public abstract class BackgroundFileProcessor<T> : IDisposable where T : class
Expand All @@ -49,17 +40,17 @@ public abstract class BackgroundFileProcessor<T> : IDisposable where T : class
protected readonly IEnumerable<FileType> _typesOfFilesToProcess;
protected readonly Func<string, T> _fileDataFactory;
protected bool _restartRequested = true;
protected Dictionary<string, T> _fileToDataDictionary = new Dictionary<string, T>();
protected Dictionary<string, T> _fileToDataDictionary = new();
private readonly Queue<FileSystemEventArgs> _pendingFileEvents;
private volatile int _suspendEventProcessingCount;
private readonly object _lockObj = new object();
private readonly object _lockSuspendObj = new object();
private readonly object _lockObj = new();
private readonly object _lockSuspendObj = new();

public event EventHandler NewDataAvailable;
public event EventHandler FinishedProcessingAllFiles;

/// ------------------------------------------------------------------------------------
public BackgroundFileProcessor(string rootDirectoryPath,
protected BackgroundFileProcessor(string rootDirectoryPath,
IEnumerable<FileType> typesOfFilesToProcess, Func<string, T> fileDataFactory)
{
RootDirectoryPath = rootDirectoryPath;
Expand All @@ -72,8 +63,7 @@ public BackgroundFileProcessor(string rootDirectoryPath,
/// ------------------------------------------------------------------------------------
public void Dispose()
{
if (_workerThread != null)
_workerThread.Abort(); //will eventually lead to it stopping
_workerThread?.Abort(); //will eventually lead to it stopping
_workerThread = null;
}

Expand Down Expand Up @@ -122,39 +112,37 @@ public virtual void ResumeProcessing(bool processAllPendingEventsNow)
protected virtual bool GetDoIncludeFile(string path)
{
var fileName = Path.GetFileName(path);
return (fileName != null && !fileName.StartsWith(".") &&
(_typesOfFilesToProcess.Any(t => t.IsMatch(path))));
return fileName != null && !fileName.StartsWith(".") &&
_typesOfFilesToProcess.Any(t => t.IsMatch(path));
}

/// ------------------------------------------------------------------------------------
protected virtual ThreadPriority ThreadPriority
{
get { return ThreadPriority.Lowest; }
}
protected virtual ThreadPriority ThreadPriority => ThreadPriority.Lowest;

/// ------------------------------------------------------------------------------------
public virtual void Start()
{
_workerThread = new Thread(StartWorking);
_workerThread.Name = GetType().Name;
_workerThread.Priority = ThreadPriority;
_workerThread = new Thread(StartWorking)
{
Name = GetType().Name,
Priority = ThreadPriority
};
_workerThread.TrySetApartmentState(ApartmentState.STA);//needed in case we eventually show an error & need to talk to email.
_workerThread.Start();
}

/// ------------------------------------------------------------------------------------
protected virtual void OnNewDataAvailable(T fileData)
{
if (NewDataAvailable != null)
NewDataAvailable(this, EventArgs.Empty);
NewDataAvailable?.Invoke(this, EventArgs.Empty);
}

/// ------------------------------------------------------------------------------------
private void StartWorking()
{
try
{
Status = kWorkingStatus; //NB: this helps simplify unit tests, if go to the busy state before returning
Status = kWorkingStatus; //NB: this helps simplify unit tests, if we go to the busy state before returning

using (var watcher = new FileSystemWatcher(RootDirectoryPath))
{
Expand Down Expand Up @@ -189,11 +177,11 @@ private void StartWorking()
}
catch (ThreadAbortException)
{
//this is fine, it happens when we quit
// This is fine, it happens when we quit
}
catch (Exception error)
{
SIL.Reporting.ErrorReport.NotifyUserOfProblem(error, "Background file watching failed.");
ErrorReport.NotifyUserOfProblem(error, "Background file watching failed.");
}
}

Expand All @@ -216,13 +204,11 @@ private void ProcessFileEvent(FileSystemEventArgs fileEvent)
{
try
{
if (fileEvent is RenamedEventArgs)
if (fileEvent is RenamedEventArgs e)
{
var e = fileEvent as RenamedEventArgs;
lock (((ICollection)_fileToDataDictionary).SyncRoot)
{
T fileData;
if (_fileToDataDictionary.TryGetValue(e.OldFullPath, out fileData))
if (_fileToDataDictionary.TryGetValue(e.OldFullPath, out var fileData))
{
_fileToDataDictionary.Remove(e.OldFullPath);
_fileToDataDictionary[e.FullPath] = fileData;
Expand All @@ -244,7 +230,7 @@ private void ProcessFileEvent(FileSystemEventArgs fileEvent)
Debug.WriteLine(e.Message);
Logger.WriteEvent("Handled Exception in {0}.ProcessingFileEvent:\r\n{1}", GetType().Name, e.ToString());
#if DEBUG
SIL.Reporting.ErrorReport.NotifyUserOfProblem(e, "Error gathering data");
ErrorReport.NotifyUserOfProblem(e, "Error gathering data");
#endif
//nothing here is worth crashing over
}
Expand All @@ -255,14 +241,13 @@ public T GetFileData(string filePath)
{
lock (((ICollection)_fileToDataDictionary).SyncRoot)
{
T stats;
if (_fileToDataDictionary.TryGetValue(filePath, out stats))
if (_fileToDataDictionary.TryGetValue(filePath, out var stats))
return stats;

if (GetDoIncludeFile(filePath))
{
CollectDataForFile(filePath);
return (_fileToDataDictionary.TryGetValue(filePath, out stats) ? stats : null);
return _fileToDataDictionary.TryGetValue(filePath, out stats) ? stats : null;
}
return null;
}
Expand Down Expand Up @@ -310,7 +295,7 @@ protected virtual void CollectDataForFile(string path)
{
ErrorReport.NotifyUserOfProblem(new ShowOncePerSessionBasedOnExactMessagePolicy(), e,
string.Format(LocalizationManager.GetString("MainWindow.AutoCompleteValueGathererError",
"An error of type {0} ocurred trying to gather information from file: {1}",
"An error of type {0} occurred trying to gather information from file: {1}",
"Parameter 0 is an exception type; parameter 1 is a file name"), e.GetType(), path));
}
else
Expand Down Expand Up @@ -356,7 +341,7 @@ public virtual void ProcessAllFilesInFolder(string folder)
/// ------------------------------------------------------------------------------------
public virtual void ProcessAllFiles()
{
//now that the watcher is up and running, gather up all existing files
// Now that the watcher is up and running, gather all existing files
lock (((ICollection)_fileToDataDictionary).SyncRoot)
{
_fileToDataDictionary.Clear();
Expand Down Expand Up @@ -405,8 +390,7 @@ protected virtual void ProcessAllFiles(string topLevelFolder, bool searchSubFold

Status = kUpToDataStatus;

if (FinishedProcessingAllFiles != null)
FinishedProcessingAllFiles(this, EventArgs.Empty);
FinishedProcessingAllFiles?.Invoke(this, EventArgs.Empty);
}

private static List<string> WalkDirectoryTree(string topLevelFolder, SearchOption searchOption)
Expand All @@ -416,7 +400,7 @@ private static List<string> WalkDirectoryTree(string topLevelFolder, SearchOptio
// First, process all the files directly under this folder
try
{
// SP-879: Crash reading .DS_Store file on MacOS
// SP-879: Crash reading .DS_Store file on macOS
files = Directory.GetFiles(topLevelFolder, "*.*").Where(name =>
{
var fileName = Path.GetFileName(name);
Expand All @@ -435,13 +419,13 @@ private static List<string> WalkDirectoryTree(string topLevelFolder, SearchOptio
Debug.Print("Directory not found: " + topLevelFolder);
}

if ((files != null) && (searchOption == SearchOption.AllDirectories))
if (files != null && searchOption == SearchOption.AllDirectories)
{
// Now find all the subdirectories under this directory.
var dirs = Directory.GetDirectories(topLevelFolder);
foreach (var dir in dirs)
{
// Resursive call for each subdirectory.
// Recursive call for each subdirectory.
returnVal.AddRange(WalkDirectoryTree(dir, searchOption));
}
}
Expand All @@ -468,16 +452,10 @@ protected bool ShouldStop
}

/// ------------------------------------------------------------------------------------
public bool Busy
{
get { return Status.StartsWith(kWorkingStatus); }
}
public bool Busy => Status.StartsWith(kWorkingStatus);

/// ------------------------------------------------------------------------------------
public bool DataUpToDate
{
get { return Status == kUpToDataStatus; }
}
public bool DataUpToDate => Status == kUpToDataStatus;

/// ------------------------------------------------------------------------------------
public string Status
Expand Down
3 changes: 1 addition & 2 deletions src/SayMore/Model/Files/FieldUpdater.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,7 @@ private void FindAndUpdateFiles(ComponentFile file, string idOfFieldToFind,

var matchingFiles = GetMatchingFiles(file.FileType);

if (_fieldGatherer != null)
_fieldGatherer.SuspendProcessing();
_fieldGatherer?.SuspendProcessing();

foreach (var path in matchingFiles)
{
Expand Down
Loading
Loading