Skip to content

Commit

Permalink
Merge pull request #1361 from sillsdev/file-locator-improvements
Browse files Browse the repository at this point in the history
Renamed and reimplemented GetFromRegistryProgramThatOpensFileType
  • Loading branch information
tombogle authored Nov 14, 2024
2 parents c06c991 + 00528d3 commit 1c95083
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 59 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- [SIL.Archiving] Made MetaTranscript.WriteCorpusImdiFile asynchronous, changing its signature to return Task<bool>.
- [SIL.Archiving] Changed the name of the third parameter in ArchivingDlgViewModel.AddFileGroup from progressMessage to addingToArchiveProgressMessage.
- [SIL.Windows.Forms.Archiving] Changed Cancel Button to say Close instead in IMDIArchivingDlg.
- [SIL.Core.Desktop] Renamed GetFromRegistryProgramThatOpensFileType to GetDefaultProgramForFileType.

### Fixed
- [SIL.Archiving] Fixed typo in RampArchivingDlgViewModel for Ethnomusicology performance collection.
- [SIL.Archiving] Changed URLs that used http: to https: in resource EmptyMets.xml.
- [SIL.Core.Desktop] Implemented GetDefaultProgramForFileType (as trenamed) in a way that works on Windows 11, Mono (probably) and MacOS (untested).

### Removed

Expand Down
2 changes: 1 addition & 1 deletion SIL.Archiving/ArchivingPrograms.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public static string GetRampExeFileLocation()
if (ArchivingDlgViewModel.IsMono)
exeFile = FileLocationUtilities.LocateInProgramFiles("ramp", true);
else
exeFile = FileLocator.GetFromRegistryProgramThatOpensFileType(rampFileExtension) ??
exeFile = FileLocator.GetDefaultProgramForFileType(rampFileExtension) ??
FileLocationUtilities.LocateInProgramFiles("ramp.exe", true, "ramp");

// make sure the file exists
Expand Down
18 changes: 6 additions & 12 deletions SIL.Core.Desktop.Tests/IO/FileLocatorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,27 +30,21 @@ public void LocateFile_FileNoteFound_ReturnsEmptyString()
}

[Test]
[Platform(Exclude="Unix")]
[Category("KnownMonoIssue")]
public void GetFromRegistryProgramThatOpensFileType_SendInvalidType_ReturnsNull()
public void GetDefaultProgramForFileType_SendInvalidType_ReturnsNull()
{
Assert.IsNull(FileLocator.GetFromRegistryProgramThatOpensFileType(".blah"));
Assert.IsNull(FileLocator.GetDefaultProgramForFileType(".blah"));
}

[Test]
[Platform(Exclude="Unix")]
[Category("KnownMonoIssue")]
public void GetFromRegistryProgramThatOpensFileType_SendValidType_ReturnsProgramPath()
public void GetDefaultProgramForFileType_SendValidType_ReturnsProgramPath()
{
Assert.IsNotNull(FileLocator.GetFromRegistryProgramThatOpensFileType(".txt"));
Assert.IsNotNull(FileLocator.GetDefaultProgramForFileType(".txt"));
}

[Test]
[Platform(Exclude="Unix")]
[Category("KnownMonoIssue")]
public void GetFromRegistryProgramThatOpensFileType_SendExtensionWithoutPeriod_ReturnsProgramPath()
public void GetDefaultProgramForFileType_SendExtensionWithoutPeriod_ReturnsProgramPath()
{
Assert.IsNotNull(FileLocator.GetFromRegistryProgramThatOpensFileType("txt"));
Assert.IsNotNull(FileLocator.GetDefaultProgramForFileType("txt"));
}
}
}
159 changes: 113 additions & 46 deletions SIL.Core.Desktop/IO/FileLocator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using JetBrains.Annotations;
using Microsoft.Win32;
using SIL.PlatformUtilities;
using SIL.Reporting;

Expand Down Expand Up @@ -150,69 +152,134 @@ public virtual IFileLocator CloneAndCustomize(IEnumerable<string> addedSearchPat
return new FileLocator(new List<string>(SearchPaths.Concat(addedSearchPaths)));
}

#region Methods for locating file in program files folders
#region Methods for locating program file associated with a file
/// ------------------------------------------------------------------------------------
/// <summary>
/// Searches the registry and returns the full path to the application program used to
/// open files having the specified extension. The fileExtension can be with or without
/// the preceding period. If the command cannot be found in the registry, then null is
/// returned. If a command in the registry is found, but it refers to a program file
/// that does not exist, null is returned.
/// returns the full path to the application program used to open files having the
/// specified extension/type. The fileExtension can be with or without
/// the preceding period. If no associated application can be found or the associated
/// program does not actually exist, null is returned.
/// </summary>
/// ------------------------------------------------------------------------------------
public static string GetFromRegistryProgramThatOpensFileType(string fileExtension)
public static string GetDefaultProgramForFileType(string fileExtension)
{
if (!Platform.IsWindows)
{
//------------------------------------------------------------------------------------
// The following command will output the mime type of an existing file, Phil.html:
// file -b --mime-type ~/Phil.html
//
// This command will tell you the default application to open the file Phil.html:
// ext=$(grep "$(file -b --mime-type ~/Phil.html)" /etc/mime.types
// | awk '{print $1}') && xdg-mime query default $ext
//
// This command will open the file Phil.html using the default application:
// xdg-open ~/Page.html
//------------------------------------------------------------------------------------

throw new NotImplementedException(
"GetFromRegistryProgramThatOpensFileType not implemented on Mono yet.");
}
if (!fileExtension.StartsWith("."))
fileExtension = "." + fileExtension;

var ext = fileExtension.Trim();
if (!ext.StartsWith("."))
ext = "." + ext;
if (Platform.IsWindows)
return GetDefaultWindowsProgramForFileType(fileExtension);

var key = Registry.ClassesRoot.OpenSubKey(ext);
if (key == null)
return null;
if (Platform.IsMac)
return GetDefaultMacProgramForFileType(fileExtension);

if (Platform.IsLinux)
return GetDefaultLinuxProgramForFileType(fileExtension);

throw new PlatformNotSupportedException("This operating system is not supported.");
}

[DllImport("Shlwapi.dll", CharSet = CharSet.Unicode)]
private static extern uint AssocQueryString(
uint flags,
int str,
string pszAssoc,
string pszExtra,
[Out] StringBuilder pszOut,
ref uint pcchOut);

private static string GetDefaultWindowsProgramForFileType(string fileExtension)
{
const int assocStrExecutable = 2;
uint length = 260;
var sb = new StringBuilder((int)length);

var value = key.GetValue(string.Empty) as string;
key.Dispose();
var result = AssocQueryString(0, assocStrExecutable, fileExtension, null, sb, ref length);

if (value == null)
if (result != 0 || sb.Length == 0)
return null;

key = Registry.ClassesRoot.OpenSubKey($"{value}\\shell\\open\\command");
var path = sb.ToString();
return Path.GetFileName(path) != "OpenWith.exe" && File.Exists(path) ? path : null;
}

if (key == null && value.ToLower() == "ramp.package")
private static string GetDefaultMacProgramForFileType(string fileExtension)
{
try
{
key = Registry.ClassesRoot.OpenSubKey("ramp\\shell\\open\\command");
if (key == null)
return null;
}
string filePath = $"/tmp/dummy{fileExtension}";
Process.Start("touch", filePath)?.WaitForExit();

value = key?.GetValue(string.Empty) as string;
key?.Dispose();
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "open",
Arguments = "-Ra " + filePath,
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
}
};

if (value == null)
return null;
process.Start();
var output = process.StandardOutput.ReadToEnd().Trim();
process.WaitForExit();

value = value.Trim('\"', '%', '1', ' ');
return (!File.Exists(value) ? null : value);
return string.IsNullOrEmpty(output) ? null : output;
}
catch
{
return null;
}
}

private static string GetDefaultLinuxProgramForFileType(string fileExtension)
{
try
{
var mimeProcess = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "xdg-mime",
Arguments = "query default " + fileExtension,
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
}
};

mimeProcess.Start();
var desktopEntry = mimeProcess.StandardOutput.ReadToEnd().Trim();
mimeProcess.WaitForExit();

if (string.IsNullOrEmpty(desktopEntry))
return null;

// Check if the executable associated with the desktop entry exists
var whichProcess = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "which",
Arguments = desktopEntry,
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
}
};

whichProcess.Start();
string executablePath = whichProcess.StandardOutput.ReadToEnd().Trim();
whichProcess.WaitForExit();

return string.IsNullOrEmpty(executablePath) ? null : executablePath;
}
catch
{
return null;
}
}
#endregion

public virtual void AddPath(string path)
Expand Down

0 comments on commit 1c95083

Please sign in to comment.