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

Renamed and reimplemented GetFromRegistryProgramThatOpensFileType #1361

Merged
merged 1 commit into from
Nov 14, 2024
Merged
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,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