Skip to content

Commit

Permalink
Mapping of extensions to repository paths in DevHome core (#3230)
Browse files Browse the repository at this point in the history
* declare appextension for git, find all source control extensions, UI changes, SDK changes, get extension information to use for mapping

* changes to map extension to registered root paths, add validation to git implementation

* changes after testing

* use serilog in validation code

* reorder using

* use published SDK version

* address PR feedback

* address PR feedback

* address PR feedback

* Minor cleanup of RepositoryTracking.cs

---------

Co-authored-by: Ryan Shepherd <ryansh@microsoft.com>
  • Loading branch information
ssparach and DefaultRyan committed Jul 31, 2024
1 parent 6e3f0b7 commit 85e8858
Show file tree
Hide file tree
Showing 18 changed files with 361 additions and 43 deletions.
2 changes: 1 addition & 1 deletion common/DevHome.Common.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Windows.CsWinRT" Version="2.0.4" />
<PackageReference Include="Microsoft.Windows.DevHome.SDK" Version="0.600.494" />
<PackageReference Include="Microsoft.Windows.DevHome.SDK" Version="0.700.544" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.240311000" />
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
Expand Down
2 changes: 1 addition & 1 deletion extensions/CoreWidgetProvider/CoreWidgetProvider.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.106">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.Windows.DevHome.SDK" Version="0.600.494" />
<PackageReference Include="Microsoft.Windows.DevHome.SDK" Version="0.700.544"/>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.240311000" />
<PackageReference Include="Serilog" Version="3.1.1" />
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ public GitLocalRepository(string rootFolder)

internal GitLocalRepository(string rootFolder, RepositoryCache? cache)
{
if (!Repository.IsValid(rootFolder))
{
throw new ArgumentException("Invalid repository path");
}

RootFolder = rootFolder;
_repositoryCache = cache;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Windows.DevHome.SDK" Version="0.600.494" />
<PackageReference Include="Microsoft.Windows.DevHome.SDK" Version="0.700.544" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Serilog" Version="3.1.1" />
Expand Down
1 change: 1 addition & 0 deletions src/Models/ExtensionWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public class ExtensionWrapper : IExtensionWrapper
[typeof(IFeaturedApplicationsProvider)] = ProviderType.FeaturedApplications,
[typeof(IComputeSystemProvider)] = ProviderType.ComputeSystem,
[typeof(IQuickStartProjectProvider)] = ProviderType.QuickStartProject,
[typeof(ILocalRepositoryProvider)] = ProviderType.LocalRepository,
};

private IExtension? _extensionObject;
Expand Down
14 changes: 14 additions & 0 deletions src/Package.appxmanifest
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,20 @@
</uap3:Properties>
</uap3:AppExtension>
</uap3:Extension>
<uap3:Extension Category="windows.appExtension">
<uap3:AppExtension Name="com.microsoft.devhome" Id="PG-SP-ID3" PublicFolder="Public" DisplayName="Git" Description="ms-resource:AppDescriptionGitExt">
<uap3:Properties>
<DevHomeProvider>
<Activation>
<CreateInstance ClassId="BDA76685-E749-4f09-8F13-C466D0802DA1" />
</Activation>
<SupportedInterfaces>
<LocalRepository />
</SupportedInterfaces>
</DevHomeProvider>
</uap3:Properties>
</uap3:AppExtension>
</uap3:Extension>
<uap3:Extension Category="windows.appExtension">
<uap3:AppExtension Name="com.microsoft.windows.widgets" DisplayName="ms-resource:WidgetProviderDisplayNameStable" Id="CoreWidgetProvider" PublicFolder="Public">
<uap3:Properties>
Expand Down
13 changes: 13 additions & 0 deletions tools/Customization/DevHome.Customization/Helpers/ErrorType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace DevHome.Customization.Helpers;

public enum ErrorType
{
None,
Unknown,
RepositoryProvderCreationFailed,
OpenRepositoryFailed,
SourceControlExtensionValidationFailed,
}
11 changes: 11 additions & 0 deletions tools/Customization/DevHome.Customization/Helpers/ResultType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace DevHome.Customization.Helpers;

public enum ResultType
{
Unknown,
Success,
Failure,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DevHome.Customization.Helpers;

public class SourceControlValidationResult
{
public ResultType Result { get; private set; } = ResultType.Unknown;

public ErrorType Error { get; private set; } = ErrorType.Unknown;

public Exception? Exception
{
get; set;
}

public string? DisplayMessage
{
get; set;
}

public string? DiagnosticText
{
get; set;
}

public SourceControlValidationResult()
{
Result = ResultType.Success;
Error = ErrorType.None;
}

public SourceControlValidationResult(ResultType result, ErrorType error, Exception? exception, string? displayMessage, string? diagnosticText)
{
Result = result;
Error = error;
Exception = exception;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Runtime.InteropServices;
using DevHome.Customization.Helpers;
using Microsoft.Windows.DevHome.SDK;
using Serilog;
using Windows.Win32;
using WinRT;

namespace DevHome.Customization.Models;

public class SourceControlIntegration
{
private static readonly Serilog.ILogger Log = Serilog.Log.ForContext("SourceContext", nameof(Models.SourceControlIntegration));

public static SourceControlValidationResult ValidateSourceControlExtension(string extensionCLSID, string rootPath)
{
var providerPtr = IntPtr.Zero;
try
{
Log.Information("Validating source control extension with arguments: extensionCLSID = {extensionCLSID}, rootPath = {rootPath}", extensionCLSID, rootPath);

var hr = PInvoke.CoCreateInstance(Guid.Parse(extensionCLSID), null, Windows.Win32.System.Com.CLSCTX.CLSCTX_LOCAL_SERVER, typeof(ILocalRepositoryProvider).GUID, out var extensionObj);
providerPtr = Marshal.GetIUnknownForObject(extensionObj);
if (hr < 0)
{
Log.Error(hr.ToString(), "Failure occurred while creating instance of repository provider");
return new SourceControlValidationResult(ResultType.Failure, ErrorType.RepositoryProvderCreationFailed, null, null, null);
}

ILocalRepositoryProvider provider = MarshalInterface<ILocalRepositoryProvider>.FromAbi(providerPtr);
GetLocalRepositoryResult result = provider.GetRepository(rootPath);

if (result.Result.Status == ProviderOperationStatus.Failure)
{
Log.Error("Could not open local repository.");
Log.Error(result.Result.DisplayMessage);
return new SourceControlValidationResult(ResultType.Failure, ErrorType.OpenRepositoryFailed, result.Result.ExtendedError, result.Result.DisplayMessage, result.Result.DiagnosticText);
}
else
{
Log.Information("Local repository opened successfully.");
}
}
catch (Exception ex)
{
Log.Error(ex, "An exception occurred while validating source control extension.");
return new SourceControlValidationResult(ResultType.Failure, ErrorType.SourceControlExtensionValidationFailed, ex, null, null);
}
finally
{
if (providerPtr != IntPtr.Zero)
{
Marshal.Release(providerPtr);
}
}

return new SourceControlValidationResult();
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
SHGetSetSettings
ReadCabinetState
WriteCabinetState
CoCreateInstance
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using DevHome.Common.Contracts.Services;
using DevHome.Common.Extensions;
using DevHome.Common.Models;
using DevHome.Common.Services;
using DevHome.Customization.ViewModels;
using Microsoft.UI.Xaml;
using Microsoft.Windows.DevHome.SDK;

namespace DevHome.Customization.ViewModels;

public partial class FileExplorerSourceControlIntegrationViewModel : ObservableObject
{
public ObservableCollection<Breadcrumb> Breadcrumbs { get; }

public IExtensionWrapper LocalRepositoryProvider { get; }

public string ProviderName => LocalRepositoryProvider.ExtensionDisplayName;

public FileExplorerSourceControlIntegrationViewModel(IExtensionWrapper localRepoProvider)
{
LocalRepositoryProvider = localRepoProvider;

var stringResource = new StringResource("DevHome.Customization.pri", "DevHome.Customization/Resources");
Breadcrumbs =
[
new(stringResource.GetLocalized("MainPage_Header"), typeof(MainPageViewModel).FullName!),
new(stringResource.GetLocalized("FileExplorer_Header"), typeof(FileExplorerViewModel).FullName!)
];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using DevHome.Common.Extensions;
using DevHome.Common.Models;
Expand All @@ -14,6 +16,7 @@
using DevHome.FileExplorerSourceControlIntegration.Services;
using Microsoft.Internal.Windows.DevHome.Helpers;
using Microsoft.UI.Xaml;
using Microsoft.Windows.DevHome.SDK;

namespace DevHome.Customization.ViewModels;

Expand All @@ -27,6 +30,8 @@ public partial class FileExplorerViewModel : ObservableObject

private RepositoryTracking RepoTracker { get; set; } = new(null);

public ObservableCollection<FileExplorerSourceControlIntegrationViewModel> LocalRepositoryProviders { get; } = new();

public FileExplorerViewModel()
{
_shellSettings = new ShellSettings();
Expand All @@ -37,6 +42,21 @@ public FileExplorerViewModel()
new(stringResource.GetLocalized("MainPage_Header"), typeof(MainPageViewModel).FullName!),
new(stringResource.GetLocalized("FileExplorer_Header"), typeof(FileExplorerViewModel).FullName!)
];

var experimentationService = Application.Current.GetService<IExperimentationService>();
if (experimentationService.IsFeatureEnabled("FileExplorerSourceControlIntegration"))
{
// Currently, the UI displays a drop down which allows the user to select from a list of source control providers avaiable to Dev Home. This is
// subject to change per UI design specifications.
var extensionService = Application.Current.GetService<IExtensionService>();
var sourceControlExtensions = Task.Run(async () => await extensionService.GetInstalledExtensionsAsync(ProviderType.LocalRepository)).Result.ToList();
sourceControlExtensions.Sort((a, b) => string.Compare(a.ExtensionDisplayName, b.ExtensionDisplayName, System.StringComparison.OrdinalIgnoreCase));
sourceControlExtensions.ForEach((sourceControlExtension) =>
{
LocalRepositoryProviders.Add(new FileExplorerSourceControlIntegrationViewModel(sourceControlExtension));
});
}

RefreshTrackedRepositories();
}

Expand All @@ -54,10 +74,17 @@ public void RefreshTrackedRepositories()
}
}

public void AddRepositoryPath(string extension, string rootPath)
public bool AddRepositoryPath(string extensionCLSID, string rootPath)
{
var normalizedPath = rootPath.ToUpper(CultureInfo.InvariantCulture).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
RepoTracker.AddRepositoryPath(extension, normalizedPath);
var result = SourceControlIntegration.ValidateSourceControlExtension(extensionCLSID, normalizedPath);
if (result.Result == Helpers.ResultType.Success)
{
RepoTracker.AddRepositoryPath(extensionCLSID, normalizedPath);
return true;
}

return false;
}

public bool ShowFileExtensions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,21 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:commonViews="using:DevHome.Common.Views"
xmlns:behaviors="using:DevHome.Common.Behaviors"
xmlns:views="using:DevHome.Customization.Views"
xmlns:views="using:DevHome.Customization.Views"
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
xmlns:viewmodels="using:DevHome.Customization.ViewModels"
behaviors:NavigationViewHeaderBehavior.HeaderTemplate="{StaticResource BreadcrumbBarDataTemplate}"
behaviors:NavigationViewHeaderBehavior.HeaderContext="{x:Bind ViewModel}">

<Page.Resources>
<converters:DoubleToVisibilityConverter x:Key="CountToVisibilityConverter" GreaterThan="0" FalseValue="Collapsed" TrueValue="Visible"/>

<DataTemplate x:Key="LocalRepositoryProviderButtonTemplate" x:DataType="viewmodels:FileExplorerSourceControlIntegrationViewModel">
<Button Content="{x:Bind ProviderName}" HorizontalAlignment="Stretch" Click="AddRepository_Click" Tag="{x:Bind}"/>
</DataTemplate>

</Page.Resources>

<ScrollViewer VerticalAlignment="Top" VerticalScrollBarVisibility="Auto">
<Grid MaxWidth="{ThemeResource MaxPageContentWidth}" Margin="{ThemeResource ContentPageMargin}">
<Grid.RowDefinitions>
Expand All @@ -26,9 +37,29 @@
MinWidth="300"
HorizontalAlignment="Left"/>

<Button Content="Add"
<Button
Content="Add"
Click="AddButton_Click"
Margin="10, 27, 0, 0"/>
Margin="10, 27, 0, 0">
<Button.Resources>
<Flyout x:Name="LocalRepositoryProvidersFlyout" Placement="Bottom">
<ItemsRepeater ItemsSource="{x:Bind ViewModel.LocalRepositoryProviders}"
ItemTemplate="{StaticResource LocalRepositoryProviderButtonTemplate}"
HorizontalAlignment="Stretch" VerticalAlignment="Center">
<ItemsRepeater.Layout>
<StackLayout Orientation="Vertical" Spacing="8" />
</ItemsRepeater.Layout>
</ItemsRepeater>
<Flyout.FlyoutPresenterStyle>
<Style TargetType="FlyoutPresenter">
<Setter Property="IsTabStop" Value="True"/>
<Setter Property="TabNavigation" Value="Cycle"/>
<Setter Property="MinWidth" Value="150" />
</Style>
</Flyout.FlyoutPresenterStyle>
</Flyout>
</Button.Resources>
</Button>
<InfoBar x:Name="RootPathErrorBar"
x:Uid="ErrorRootPathValidation"
Title="Error"
Expand Down
Loading

0 comments on commit 85e8858

Please sign in to comment.