Add separate lib to hold mef interfaces with reactive dependencies

This commit is contained in:
Andreia Gaita 2015-02-24 21:52:25 +01:00
Родитель b3fa798524
Коммит d97c92633f
11 изменённых файлов: 619 добавлений и 0 удалений

Просмотреть файл

@ -0,0 +1,10 @@
using System;
using Octokit;
namespace GitHub.Authentication
{
public interface ITwoFactorChallengeHandler
{
IObservable<TwoFactorChallengeResult> HandleTwoFactorException(TwoFactorRequiredException exception);
}
}

Просмотреть файл

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Reactive;
using Octokit;
namespace GitHub
{
/// <summary>
/// Per host cache data.
/// </summary>
public interface IHostCache : IDisposable
{
IObservable<User> GetUser();
IObservable<Unit> InsertUser(User user);
IObservable<IEnumerable<Organization>> GetAllOrganizations();
IObservable<Unit> InsertOrganization(Organization organization);
IObservable<Unit> InvalidateOrganization(Organization organization);
IObservable<Unit> InvalidateOrganization(IAccount organization);
IObservable<Unit> InvalidateAll();
}
}

Просмотреть файл

@ -0,0 +1,104 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{E4ED0537-D1D9-44B6-9212-3096D7C3F7A1}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>GitHub.Exports.Reactive</RootNamespace>
<AssemblyName>GitHub.Exports.Reactive</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Octokit">
<HintPath>..\..\packages\Octokit.0.6.2\lib\net45\Octokit.dll</HintPath>
</Reference>
<Reference Include="PresentationCore" />
<Reference Include="ReactiveUI">
<HintPath>..\..\packages\reactiveui-core.6.3.1\lib\Net45\ReactiveUI.dll</HintPath>
</Reference>
<Reference Include="Splat">
<HintPath>..\..\packages\Splat.1.6.0\lib\Net45\Splat.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Reactive.Core">
<HintPath>..\..\packages\Rx-Core.2.2.5\lib\net45\System.Reactive.Core.dll</HintPath>
</Reference>
<Reference Include="System.Reactive.Interfaces">
<HintPath>..\..\packages\Rx-Interfaces.2.2.5\lib\net45\System.Reactive.Interfaces.dll</HintPath>
</Reference>
<Reference Include="System.Reactive.Linq">
<HintPath>..\..\packages\Rx-Linq.2.2.5\lib\net45\System.Reactive.Linq.dll</HintPath>
</Reference>
<Reference Include="System.Reactive.PlatformServices">
<HintPath>..\..\packages\Rx-PlatformServices.2.2.5\lib\net45\System.Reactive.PlatformServices.dll</HintPath>
</Reference>
<Reference Include="System.Reactive.Windows.Threading">
<HintPath>..\..\packages\Rx-XAML.2.2.5\lib\net45\System.Reactive.Windows.Threading.dll</HintPath>
</Reference>
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\script\SolutionInfo.cs">
<Link>Properties\SolutionInfo.cs</Link>
</Compile>
<Compile Include="Authentication\ITwoFactorChallengeHandler.cs" />
<Compile Include="Caches\IHostCache.cs" />
<Compile Include="Models\IAccount.cs" />
<Compile Include="Models\IRepositoryHost.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Services\IApiClient.cs" />
<Compile Include="Services\IAvatarProvider.cs" />
<Compile Include="UI\ICreateRepoViewModel.cs" />
<Compile Include="Validation\ReactivePropertyValidator.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GitHub.Exports\GitHub.Exports.csproj">
<Project>{9aea02db-02b5-409c-b0ca-115d05331a6b}</Project>
<Name>GitHub.Exports</Name>
</ProjectReference>
<ProjectReference Include="..\GitHub.Extensions\GitHub.Extensions.csproj">
<Project>{6afe2e2d-6db0-4430-a2ea-f5f5388d2f78}</Project>
<Name>GitHub.Extensions</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

Просмотреть файл

@ -0,0 +1,38 @@
using GitHub.Models;
using Octokit;
using ReactiveUI;
namespace GitHub
{
public interface IAccount
{
string Email { get; }
int Id { get; }
bool IsEnterprise { get; }
bool IsGitHub { get; }
bool IsLocal { get; }
bool IsOnFreePlan { get; }
bool HasMaximumPrivateRepositories { get; }
bool IsUser { get; }
/// <summary>
/// True if the user is an admin on the host (GitHub or Enterprise).
/// </summary>
/// <remarks>
/// Do not confuse this with "IsStaff". This is true if the user is an admin
/// on the site. IsStaff is true if that site is github.com.
/// </remarks>
bool IsSiteAdmin { get; }
/// <summary>
/// Returns true if the user is a member of the GitHub staff.
/// </summary>
bool IsGitHubStaff { get; }
IRepositoryHost Host { get; }
string Login { get; }
string Name { get; }
int OwnedPrivateRepos { get; }
long PrivateReposInPlan { get; }
void Update(User ghUser);
void Update(Organization org);
}
}

Просмотреть файл

@ -0,0 +1,29 @@
using System;
using System.Reactive;
using ReactiveUI;
using GitHub.Authentication;
namespace GitHub.Models
{
public interface IRepositoryHost
{
HostAddress Address { get; }
IApiClient ApiClient { get; }
IHostCache Cache { get; }
bool IsGitHub { get; }
bool IsLoggedIn { get; }
bool IsLoggingIn { get; }
bool IsEnterprise { get; }
bool IsLocal { get; }
ReactiveList<IAccount> Organizations { get; }
ReactiveList<IAccount> Accounts { get; }
string Title { get; }
IAccount User { get; }
IObservable<AuthenticationResult> LogIn(string usernameOrEmail, string password);
IObservable<AuthenticationResult> LogInFromCache();
IObservable<Unit> LogOut();
IObservable<Unit> Refresh();
IObservable<Unit> Refresh(Func<IRepositoryHost, IObservable<Unit>> refreshTrackedRepositoriesFunc);
}
}

Просмотреть файл

@ -0,0 +1,6 @@
using System.Reflection;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("GitHub.Exports.Reactive")]
[assembly: AssemblyDescription("GitHub interfaces for mef exports with reactive dependencies")]
[assembly: Guid("e4ed0537-d1d9-44b6-9212-3096d7c3f7a1")]

Просмотреть файл

@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using GitHub.Authentication;
using Octokit;
namespace GitHub
{
public interface IApiClient
{
HostAddress HostAddress { get; }
IObservable<SshKey> AddSshKey(SshKey newKey);
IObservable<Repository> CreateRepository(Repository repo, string login, bool isUser);
IObservable<SshKey> GetSshKeys();
IObservable<User> GetUser();
IObservable<User> GetAllUsersForAllOrganizations();
IObservable<Organization> GetOrganization(string login);
IObservable<Organization> GetOrganizations();
IObservable<User> GetMembersOfOrganization(string organizationName);
IObservable<Repository> GetRepository(string owner, string name);
IObservable<IEnumerable<Repository>> GetUserRepositories(int currentUserId);
IObservable<Repository> GetCurrentUserRepositoriesStreamed();
IObservable<Repository> GetOrganizationRepositoriesStreamed(string login);
IObservable<Authorization> GetOrCreateApplicationAuthenticationCode(
Func<TwoFactorRequiredException, IObservable<TwoFactorChallengeResult>> twoFactorChallengeHander = null,
bool useOldScopes = false);
IObservable<Authorization> GetOrCreateApplicationAuthenticationCode(
string authenticationCode,
bool useOldScopes = false);
IObservable<IReadOnlyList<EmailAddress>> GetEmails();
ITwoFactorChallengeHandler TwoFactorChallengeHandler { get; }
}
}

Просмотреть файл

@ -0,0 +1,21 @@
using System;
using System.Reactive;
using System.Windows.Media.Imaging;
using Octokit;
namespace GitHub.Services
{
public interface IHostAvatarProvider
{
IAvatarProvider Get(string gitHubBaseUrl);
}
public interface IAvatarProvider
{
BitmapImage DefaultUserBitmapImage { get; }
BitmapImage DefaultOrgBitmapImage { get; }
IObservable<BitmapSource> GetAvatar(Account apiAccount);
IObservable<Unit> InvalidateAvatar(Account apiAccount);
IObservable<BitmapSource> GetAvatar(string email);
}
}

Просмотреть файл

@ -0,0 +1,35 @@
using GitHub.Validation;
using ReactiveUI;
using System;
using System.Reactive;
using System.Windows.Input;
namespace GitHub.UI
{
public interface ICreateRepoViewModel
{
string RepositoryName { get; }
string SafeRepositoryName { get; }
bool ShowRepositoryNameWarning { get; }
string RepositoryNameWarningText { get; }
ReactivePropertyValidator<string> RepositoryNameValidator { get; }
string Description { get; set; }
ReactiveList<IAccount> Accounts { get; }
IAccount SelectedAccount { get; }
bool KeepPrivate { get; set; }
bool CanKeepPrivate { get; }
bool ShowUpgradeToMicroPlanWarning { get; }
bool ShowUpgradePlanWarning { get; }
bool IsPublishing { get; }
ReactiveCommand<Unit> CreateRepository { get; }
ReactiveCommand<Object> UpgradeAccountPlan { get; }
ReactiveCommand<Object> Reset { get; }
ICommand OkCmd { get; }
ICommand CancelCmd { get; }
}
}

Просмотреть файл

@ -0,0 +1,309 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reactive.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using GitHub.Extensions;
using ReactiveUI;
namespace GitHub.Validation
{
public class ReactivePropertyValidationResult
{
public bool IsValid { get; private set; }
public ValidationStatus Status { get; private set; }
public string Message { get; private set; }
[SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "It is immutable")]
public static readonly ReactivePropertyValidationResult Success = new ReactivePropertyValidationResult(ValidationStatus.Valid);
[SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "It is immutable")]
public static readonly ReactivePropertyValidationResult Unvalidated = new ReactivePropertyValidationResult();
public ReactivePropertyValidationResult()
: this(ValidationStatus.Unvalidated, "")
{
}
public ReactivePropertyValidationResult(ValidationStatus validationStatus)
: this(validationStatus, "")
{
}
public ReactivePropertyValidationResult(ValidationStatus validationStatus, string message)
{
Status = validationStatus;
IsValid = validationStatus != ValidationStatus.Invalid;
Message = message;
}
}
public enum ValidationStatus
{
Unvalidated = 0,
Invalid = 1,
Valid = 2,
}
public abstract class ReactivePropertyValidator : ReactiveObject
{
public static ReactivePropertyValidator<TProp> For<TObj, TProp>(TObj This, Expression<Func<TObj, TProp>> property)
{
return new ReactivePropertyValidator<TObj, TProp>(This, property);
}
public abstract ReactivePropertyValidationResult ValidationResult { get; protected set; }
public abstract bool IsValidating { get; }
protected ReactivePropertyValidator()
{
}
public abstract Task ExecuteAsync();
public abstract Task ResetAsync();
}
[SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")]
public class ReactivePropertyValidator<TProp> : ReactivePropertyValidator
{
readonly ReactiveCommand<ReactivePropertyValidationResult> validateCommand;
ReactivePropertyValidationResult validationResult;
public override ReactivePropertyValidationResult ValidationResult
{
get { return validationResult; }
protected set { this.RaiseAndSetIfChanged(ref validationResult, value); }
}
public override Task ExecuteAsync()
{
return validateCommand.ExecuteAsyncTask(new ValidationParameter());
}
public override Task ResetAsync()
{
return validateCommand.ExecuteAsyncTask(new ValidationParameter { RequiresReset = true });
}
readonly List<Func<TProp, IObservable<ReactivePropertyValidationResult>>> validators =
new List<Func<TProp, IObservable<ReactivePropertyValidationResult>>>();
readonly ObservableAsPropertyHelper<bool> isValidating;
public override bool IsValidating
{
get { return isValidating.Value; }
}
public ReactivePropertyValidator(IObservable<TProp> signal)
{
validateCommand = ReactiveCommand.CreateAsyncObservable(param =>
{
var validationParams = (ValidationParameter)param;
if (validationParams.RequiresReset)
{
return Observable.Return(ReactivePropertyValidationResult.Unvalidated);
}
TProp value = validationParams.PropertyValue;
var currentValidators = validators.ToList();
// HEAR YE, HEAR YE
// This .ToList() is here to ignore changes to the validator collection,
// and thus avoid fantastically vague exceptions about
// "Collection was modified, enumeration operation may not execute"
// bubbling up to tear the application down
// Thus, the collection will be correct when the command executes,
// which should be fine until we need to do more complex validation
if (!currentValidators.Any())
return Observable.Return(ReactivePropertyValidationResult.Unvalidated);
return currentValidators.ToObservable()
.SelectMany(v => v(value))
.FirstOrDefaultAsync(x => x.Status == ValidationStatus.Invalid)
.Select(x => x == null ? ReactivePropertyValidationResult.Success : x);
});
isValidating = validateCommand.IsExecuting.ToProperty(this, x => x.IsValidating);
validateCommand.Subscribe(x => ValidationResult = x);
signal.Subscribe(x => validateCommand.Execute(new ValidationParameter { PropertyValue = x, RequiresReset = false }));
}
public ReactivePropertyValidator<TProp> IfTrue(Func<TProp, bool> predicate, string errorMessage)
{
return Add(predicate, errorMessage);
}
public ReactivePropertyValidator<TProp> IfFalse(Func<TProp, bool> predicate, string errorMessage)
{
return Add(x => !predicate(x), errorMessage);
}
ReactivePropertyValidator<TProp> Add(Func<TProp, bool> predicate, string errorMessage)
{
return Add(x => predicate(x) ? errorMessage : null);
}
public ReactivePropertyValidator<TProp> Add(Func<TProp, string> predicateWithMessage)
{
validators.Add(value => Observable.Defer(() => Observable.Return(Validate(value, predicateWithMessage))));
return this;
}
public ReactivePropertyValidator<TProp> IfTrueAsync(Func<TProp, IObservable<bool>> predicate, string errorMessage)
{
AddAsync(x => predicate(x).Select(result => result ? errorMessage : null));
return this;
}
public ReactivePropertyValidator<TProp> IfFalseAsync(Func<TProp, IObservable<bool>> predicate, string errorMessage)
{
AddAsync(x => predicate(x).Select(result => result ? null : errorMessage));
return this;
}
public ReactivePropertyValidator<TProp> AddAsync(Func<TProp, IObservable<string>> predicateWithMessage)
{
validators.Add(value => Observable.Defer(() =>
{
return predicateWithMessage(value)
.Select(result => String.IsNullOrEmpty(result)
? ReactivePropertyValidationResult.Success
: new ReactivePropertyValidationResult(ValidationStatus.Invalid, result));
}));
return this;
}
static ReactivePropertyValidationResult Validate(TProp value, Func<TProp, string> predicateWithMessage)
{
var result = predicateWithMessage(value);
if (String.IsNullOrEmpty(result))
return ReactivePropertyValidationResult.Success;
return new ReactivePropertyValidationResult(ValidationStatus.Invalid, result);
}
class ValidationParameter
{
public TProp PropertyValue { get; set; }
public bool RequiresReset { get; set; }
}
}
public class ReactivePropertyValidator<TObj, TProp> : ReactivePropertyValidator<TProp>
{
protected ReactivePropertyValidator()
: base(Observable.Empty<TProp>())
{
}
public ReactivePropertyValidator(TObj This, Expression<Func<TObj, TProp>> property)
: base(This.WhenAny(property, x => x.Value)) { }
}
public static class ReactivePropertyValidatorExtensions
{
public static ReactivePropertyValidator<string> IfMatch(this ReactivePropertyValidator<string> This, string pattern, string errorMessage)
{
var regex = new Regex(pattern);
return This.IfTrue(regex.IsMatch, errorMessage);
}
public static ReactivePropertyValidator<string> IfNotMatch(this ReactivePropertyValidator<string> This, string pattern, string errorMessage)
{
var regex = new Regex(pattern);
return This.IfFalse(regex.IsMatch, errorMessage);
}
public static ReactivePropertyValidator<string> IfNullOrEmpty(this ReactivePropertyValidator<string> This, string errorMessage)
{
return This.IfTrue(String.IsNullOrEmpty, errorMessage);
}
public static ReactivePropertyValidator<string> IfNotUri(this ReactivePropertyValidator<string> This, string errorMessage)
{
return This.IfFalse(s =>
{
Uri uri;
return Uri.TryCreate(s, UriKind.Absolute, out uri);
}, errorMessage);
}
public static ReactivePropertyValidator<string> IfSameAsHost(this ReactivePropertyValidator<string> This, Uri compareToHost, string errorMessage)
{
return This.IfTrue(s =>
{
Uri uri;
var isUri = Uri.TryCreate(s, UriKind.Absolute, out uri);
return isUri && uri.IsSameHost(compareToHost);
}, errorMessage);
}
public static ReactivePropertyValidator<string> IfContainsInvalidPathChars(this ReactivePropertyValidator<string> This, string errorMessage)
{
return This.IfTrue(str =>
{
// easiest check to make
if (str.ContainsAny(Path.GetInvalidPathChars()))
{
return true;
}
string driveLetter;
try
{
// if for whatever reason you don't have an absolute path
// hopefully you've remembered to use `IfPathNotRooted`
// in your validator
driveLetter = Path.GetPathRoot(str);
}
catch (ArgumentException)
{
// Path.GetPathRoot does some fun things
// around legal combinations of characters that we miss
// by simply checking against an array of legal characters
return true;
}
if (driveLetter == null)
{
return false;
}
// lastly, check each directory name doesn't contain
// any invalid filename characters
var foldersInPath = str.Substring(driveLetter.Length);
return foldersInPath.Split(new[] { '\\', '/' }, StringSplitOptions.None)
.Any(x => x.ContainsAny(Path.GetInvalidFileNameChars()));
}, errorMessage);
}
public static ReactivePropertyValidator<string> IfPathNotRooted(this ReactivePropertyValidator<string> This, string errorMessage)
{
return This.IfFalse(Path.IsPathRooted, errorMessage);
}
public static ReactivePropertyValidator<string> IfUncPath(this ReactivePropertyValidator<string> This, string errorMessage)
{
return This.IfTrue(str => str.StartsWith(@"\\", StringComparison.Ordinal), errorMessage);
}
}
}

Просмотреть файл

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Octokit" version="0.6.2" targetFramework="net45" />
<package id="reactiveui" version="6.3.1" targetFramework="net45" />
<package id="reactiveui-core" version="6.3.1" targetFramework="net45" />
<package id="Rx-Core" version="2.2.5" targetFramework="net45" />
<package id="Rx-Interfaces" version="2.2.5" targetFramework="net45" />
<package id="Rx-Linq" version="2.2.5" targetFramework="net45" />
<package id="Rx-Main" version="2.2.5" targetFramework="net45" />
<package id="Rx-PlatformServices" version="2.2.5" targetFramework="net45" />
<package id="Rx-XAML" version="2.2.5" targetFramework="net45" />
<package id="Splat" version="1.6.0" targetFramework="net45" />
</packages>