Deterministic items order, OneTime binding fix (#25)
* Inject model into providers, order by time * Fix Avalonia OneTime binding bug * Update UWP picture
This commit is contained in:
Родитель
7fb5ab81bd
Коммит
768933e172
|
@ -8,6 +8,7 @@ using Camelotia.Presentation.ViewModels;
|
|||
using Camelotia.Services.Providers;
|
||||
using Camelotia.Services.Storages;
|
||||
using Camelotia.Services.Interfaces;
|
||||
using Camelotia.Services.Models;
|
||||
using ReactiveUI;
|
||||
using Avalonia;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
@ -48,7 +49,7 @@ namespace Camelotia.Presentation.Avalonia
|
|||
provider, current, main
|
||||
),
|
||||
new ProviderStorage(
|
||||
new Dictionary<string, Func<Guid, IProvider>>
|
||||
new Dictionary<string, Func<ProviderModel, IProvider>>
|
||||
{
|
||||
["Local File System"] = id => new LocalProvider(id),
|
||||
["Vkontakte Docs"] = id => new VkDocsProvider(id, cache),
|
||||
|
|
|
@ -59,12 +59,12 @@
|
|||
<DataTemplate DataType="interfaces:IProviderViewModel">
|
||||
<Grid>
|
||||
<StackPanel Margin="10">
|
||||
<TextBlock Text="{Binding Name, Mode=OneTime}"
|
||||
<TextBlock Text="{Binding Name}"
|
||||
Foreground="White"
|
||||
FontSize="15" />
|
||||
<StackPanel Orientation="Horizontal" Margin="0 5">
|
||||
<TextBlock Text="Size: " Foreground="#eeeeee" />
|
||||
<TextBlock Text="{Binding Size, Mode=OneTime}" Foreground="#eeeeee" />
|
||||
<TextBlock Text="{Binding Size}" Foreground="#eeeeee" />
|
||||
</StackPanel>
|
||||
<TextBlock Text="{Binding Description}"
|
||||
TextWrapping="Wrap"
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<PackageReference Include="FluentAssertions" Version="5.6.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.1.0" />
|
||||
<PackageReference Include="ReactiveUI.Testing" Version="9.12.1" />
|
||||
<PackageReference Include="NSubstitute" Version="4.1.0" />
|
||||
<PackageReference Include="NSubstitute" Version="4.2.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
|
|
@ -3,6 +3,7 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Camelotia.Services.Interfaces;
|
||||
using Camelotia.Services.Models;
|
||||
using Camelotia.Services.Providers;
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
@ -13,14 +14,24 @@ namespace Camelotia.Presentation.Tests
|
|||
{
|
||||
private static readonly Guid LocalIdentifier = Guid.NewGuid();
|
||||
private static readonly string Separator = Path.DirectorySeparatorChar.ToString();
|
||||
private readonly IProvider _provider = new LocalProvider(LocalIdentifier);
|
||||
private readonly IProvider _provider = new LocalProvider(new ProviderModel
|
||||
{
|
||||
Id = LocalIdentifier,
|
||||
Created = DateTime.Now,
|
||||
Type = "Local"
|
||||
});
|
||||
|
||||
[Fact]
|
||||
public void ShouldExposeCorrectId() => _provider.Id.Should().Be(LocalIdentifier);
|
||||
|
||||
public void ShouldExposeCorrectId()
|
||||
{
|
||||
_provider.Name.Should().Be("Local");
|
||||
_provider.Id.Should().Be(LocalIdentifier);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LocalFileSystemShouldNotSupportAuth()
|
||||
{
|
||||
_provider.SupportsHostAuth.Should().BeFalse();
|
||||
_provider.SupportsDirectAuth.Should().BeFalse();
|
||||
_provider.SupportsOAuth.Should().BeFalse();
|
||||
await _provider.DirectAuth(string.Empty, string.Empty);
|
||||
|
@ -56,6 +67,10 @@ namespace Camelotia.Presentation.Tests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldImplementNonNullInitialPath() => _provider.InitialPath.Should().NotBeNull();
|
||||
public void ShouldImplementNonNullInitialPath()
|
||||
{
|
||||
_provider.InitialPath.Should().NotBeNull();
|
||||
_provider.InitialPath.Should().Be(string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,9 +22,7 @@ namespace Camelotia.Presentation.Tests
|
|||
[Fact]
|
||||
public void ShouldIndicateWhenLoadingAndReady() => new TestScheduler().With(scheduler =>
|
||||
{
|
||||
_providerStorage
|
||||
.Providers()
|
||||
.Returns(Observable.Return(new ChangeSet<IProvider, Guid>()));
|
||||
_providerStorage.Read().Returns(Observable.Return(new ChangeSet<IProvider, Guid>()));
|
||||
|
||||
var model = BuildMainViewModel(scheduler);
|
||||
model.IsLoading.Should().BeFalse();
|
||||
|
@ -50,7 +48,7 @@ namespace Camelotia.Presentation.Tests
|
|||
var collection = new ObservableCollectionExtended<IProvider>();
|
||||
var set = collection.ToObservableChangeSet(x => x.Id);
|
||||
|
||||
_providerStorage.Providers().Returns(set);
|
||||
_providerStorage.Read().Returns(set);
|
||||
_providerStorage
|
||||
.When(storage => storage.Refresh())
|
||||
.Do(args => collection.Add(Substitute.For<IProvider>()));
|
||||
|
@ -72,7 +70,7 @@ namespace Camelotia.Presentation.Tests
|
|||
var collection = new ObservableCollectionExtended<IProvider>();
|
||||
var set = collection.ToObservableChangeSet(x => x.Id);
|
||||
|
||||
_providerStorage.Providers().Returns(set);
|
||||
_providerStorage.Read().Returns(set);
|
||||
_providerStorage
|
||||
.When(storage => storage.Refresh())
|
||||
.Do(args => collection.Add(Substitute.For<IProvider>()));
|
||||
|
@ -92,9 +90,9 @@ namespace Camelotia.Presentation.Tests
|
|||
public void ShouldUnselectSelectedProvider() => new TestScheduler().With(scheduler =>
|
||||
{
|
||||
var collection = new ObservableCollectionExtended<IProvider>();
|
||||
var set = collection.ToObservableChangeSet(x => x.Id);
|
||||
var changes = collection.ToObservableChangeSet(x => x.Id);
|
||||
|
||||
_providerStorage.Providers().Returns(set);
|
||||
_providerStorage.Read().Returns(changes);
|
||||
_providerStorage
|
||||
.When(storage => storage.Refresh())
|
||||
.Do(args => collection.Add(Substitute.For<IProvider>()));
|
||||
|
@ -116,10 +114,53 @@ namespace Camelotia.Presentation.Tests
|
|||
model.SelectedProvider.Should().BeNull();
|
||||
});
|
||||
|
||||
private MainViewModel BuildMainViewModel(IScheduler scheduler)
|
||||
[Fact]
|
||||
public void ShouldOrderProvidersBasedOnDateAdded() => new TestScheduler().With(scheduler =>
|
||||
{
|
||||
var collection = new ObservableCollectionExtended<IProvider>
|
||||
{
|
||||
BuildProviderCreatedAt(new DateTime(2000, 1, 1, 1, 1, 1)),
|
||||
BuildProviderCreatedAt(new DateTime(2015, 1, 1, 1, 1, 1)),
|
||||
BuildProviderCreatedAt(new DateTime(2010, 1, 1, 1, 1, 1))
|
||||
};
|
||||
var changes = collection.ToObservableChangeSet(x => x.Id);
|
||||
_providerStorage.Read().Returns(changes);
|
||||
|
||||
var model = BuildMainViewModel(scheduler, (provider, _, __) =>
|
||||
{
|
||||
var id = provider.Id;
|
||||
var created = provider.Created;
|
||||
var entry = Substitute.For<IProviderViewModel>();
|
||||
entry.Created.Returns(created);
|
||||
entry.Id.Returns(id);
|
||||
return entry;
|
||||
});
|
||||
|
||||
model.Providers.Should().BeEmpty();
|
||||
model.Refresh.Execute(null);
|
||||
scheduler.AdvanceBy(3);
|
||||
|
||||
model.Providers.Should().NotBeEmpty();
|
||||
model.Providers.Count.Should().Be(3);
|
||||
|
||||
model.Providers[0].Created.Should().Be(new DateTime(2015, 1, 1, 1, 1, 1));
|
||||
model.Providers[1].Created.Should().Be(new DateTime(2010, 1, 1, 1, 1, 1));
|
||||
model.Providers[2].Created.Should().Be(new DateTime(2000, 1, 1, 1, 1, 1));
|
||||
});
|
||||
|
||||
private IProvider BuildProviderCreatedAt(DateTime date)
|
||||
{
|
||||
var id = Guid.NewGuid();
|
||||
var provider = Substitute.For<IProvider>();
|
||||
provider.Created.Returns(date);
|
||||
provider.Id.Returns(id);
|
||||
return provider;
|
||||
}
|
||||
|
||||
private MainViewModel BuildMainViewModel(IScheduler scheduler, ProviderViewModelFactory factory = null)
|
||||
{
|
||||
return new MainViewModel(
|
||||
(provider, files, auth) => Substitute.For<IProviderViewModel>(),
|
||||
factory ?? ((provider, files, auth) => Substitute.For<IProviderViewModel>()),
|
||||
provider => Substitute.For<IAuthViewModel>(),
|
||||
_providerStorage,
|
||||
_fileManager,
|
||||
|
|
|
@ -22,7 +22,7 @@ namespace Camelotia.Presentation.Tests
|
|||
[Fact]
|
||||
public async Task ShouldResolveAllSupportedProviders()
|
||||
{
|
||||
var provider = new ProviderStorage(new Dictionary<string, Func<Guid, IProvider>>
|
||||
var provider = new ProviderStorage(new Dictionary<string, Func<ProviderModel, IProvider>>
|
||||
{
|
||||
[typeof(LocalProvider).Name] = id => new LocalProvider(id),
|
||||
[typeof(VkDocsProvider).Name] = id => new VkDocsProvider(id, _blobCache),
|
||||
|
@ -32,7 +32,7 @@ namespace Camelotia.Presentation.Tests
|
|||
await provider.Add(typeof(LocalProvider).Name);
|
||||
await provider.Add(typeof(VkDocsProvider).Name);
|
||||
await provider.Add(typeof(YandexDiskProvider).Name);
|
||||
var providers = provider.Providers().AsAggregator();
|
||||
var providers = provider.Read().AsAggregator();
|
||||
|
||||
Assert.Equal(3, providers.Data.Count);
|
||||
Assert.Contains(providers.Data.Items, x => x is LocalProvider);
|
||||
|
@ -43,13 +43,13 @@ namespace Camelotia.Presentation.Tests
|
|||
[Fact]
|
||||
public async Task ShouldResolveOnlySpecifiedProvidersIfNeeded()
|
||||
{
|
||||
var provider = new ProviderStorage(new Dictionary<string, Func<Guid, IProvider>>
|
||||
var provider = new ProviderStorage(new Dictionary<string, Func<ProviderModel, IProvider>>
|
||||
{
|
||||
[typeof(LocalProvider).Name] = id => new LocalProvider(id),
|
||||
}, _blobCache);
|
||||
|
||||
await provider.Add(typeof(LocalProvider).Name);
|
||||
var providers = provider.Providers().AsAggregator();
|
||||
var providers = provider.Read().AsAggregator();
|
||||
|
||||
Assert.Equal(1, providers.Data.Count);
|
||||
Assert.Contains(providers.Data.Items, x => x is LocalProvider);
|
||||
|
@ -60,13 +60,13 @@ namespace Camelotia.Presentation.Tests
|
|||
[Fact]
|
||||
public async Task ShouldRemoveProviders()
|
||||
{
|
||||
var provider = new ProviderStorage(new Dictionary<string, Func<Guid, IProvider>>
|
||||
var provider = new ProviderStorage(new Dictionary<string, Func<ProviderModel, IProvider>>
|
||||
{
|
||||
[typeof(LocalProvider).Name] = id => new LocalProvider(id),
|
||||
}, _blobCache);
|
||||
|
||||
await provider.Add(typeof(LocalProvider).Name);
|
||||
var providers = provider.Providers().AsAggregator();
|
||||
var providers = provider.Read().AsAggregator();
|
||||
Assert.Equal(1, providers.Data.Count);
|
||||
|
||||
await provider.Remove(providers.Data.Items.First().Id);
|
||||
|
@ -87,13 +87,13 @@ namespace Camelotia.Presentation.Tests
|
|||
}
|
||||
}));
|
||||
|
||||
var provider = new ProviderStorage(new Dictionary<string, Func<Guid, IProvider>>
|
||||
var provider = new ProviderStorage(new Dictionary<string, Func<ProviderModel, IProvider>>
|
||||
{
|
||||
[typeof(LocalProvider).Name] = id => new LocalProvider(id),
|
||||
}, _blobCache);
|
||||
|
||||
await provider.Refresh();
|
||||
var providers = provider.Providers().AsAggregator();
|
||||
var providers = provider.Read().AsAggregator();
|
||||
Assert.Equal(1, providers.Data.Count);
|
||||
Assert.Contains(providers.Data.Items, x => x is LocalProvider);
|
||||
Assert.DoesNotContain(providers.Data.Items, x => x is VkDocsProvider);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reactive.Concurrency;
|
||||
|
@ -65,14 +66,16 @@ namespace Camelotia.Presentation.Tests
|
|||
[Fact]
|
||||
public void ShouldInheritMetaDataFromProvider() => new TestScheduler().With(scheduler =>
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
_provider.Name.Returns("Foo");
|
||||
_provider.Size.Returns(42);
|
||||
_provider.Description.Returns("Bar");
|
||||
_provider.Created.Returns(now);
|
||||
|
||||
var model = BuildProviderViewModel(scheduler);
|
||||
model.Name.Should().Be("Foo");
|
||||
model.Size.Should().Be("42B");
|
||||
model.Description.Should().Be("Bar");
|
||||
model.Description.Should().Be("Foo file system.");
|
||||
model.Created.Should().Be(now);
|
||||
});
|
||||
|
||||
[Fact]
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using Akavache;
|
||||
using Camelotia.Services.Models;
|
||||
using Camelotia.Services.Providers;
|
||||
using FluentAssertions;
|
||||
using NSubstitute;
|
||||
|
@ -9,15 +11,28 @@ namespace Camelotia.Presentation.Tests
|
|||
{
|
||||
public sealed class VkontakteFileSystemProviderTests
|
||||
{
|
||||
private static readonly Guid VkontakteIdentifier = Guid.NewGuid();
|
||||
private readonly IBlobCache _blobCache = Substitute.For<IBlobCache>();
|
||||
|
||||
private readonly ProviderModel _model = new ProviderModel
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Type = "Vkontakte",
|
||||
Created = DateTime.Now
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public void ShouldImplementNonNullInitialPath()
|
||||
{
|
||||
var provider = new VkDocsProvider(VkontakteIdentifier, _blobCache);
|
||||
provider.InitialPath.Should().NotBeNull();
|
||||
provider.Id.Should().Be(VkontakteIdentifier);
|
||||
var provider = new VkDocsProvider(_model, _blobCache);
|
||||
provider.InitialPath.Should().Be(Path.DirectorySeparatorChar.ToString());
|
||||
|
||||
provider.CanCreateFolder.Should().BeFalse();
|
||||
provider.Created.Should().Be(_model.Created);
|
||||
provider.Name.Should().Be("Vkontakte");
|
||||
provider.Id.Should().Be(_model.Id);
|
||||
|
||||
provider.SupportsDirectAuth.Should().BeTrue();
|
||||
provider.SupportsHostAuth.Should().BeFalse();
|
||||
provider.SupportsOAuth.Should().BeFalse();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using Akavache;
|
||||
using Camelotia.Services.Interfaces;
|
||||
using Camelotia.Services.Models;
|
||||
using Camelotia.Services.Providers;
|
||||
using FluentAssertions;
|
||||
using NSubstitute;
|
||||
|
@ -10,16 +12,29 @@ namespace Camelotia.Presentation.Tests
|
|||
{
|
||||
public sealed class YandexFileSystemProviderTests
|
||||
{
|
||||
private static readonly Guid YandexIdentifier = Guid.NewGuid();
|
||||
private readonly IAuthenticator _authenticator = Substitute.For<IAuthenticator>();
|
||||
private readonly IBlobCache _blobCache = Substitute.For<IBlobCache>();
|
||||
private readonly ProviderModel _model = new ProviderModel
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Type = "Yandex",
|
||||
Created = DateTime.Now
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public void ShouldImplementNonNullInitialPath()
|
||||
{
|
||||
var provider = new YandexDiskProvider(YandexIdentifier, _authenticator, _blobCache);
|
||||
provider.InitialPath.Should().NotBeNull();
|
||||
provider.Id.Should().Be(YandexIdentifier);
|
||||
var provider = new YandexDiskProvider(_model, _authenticator, _blobCache);
|
||||
provider.InitialPath.Should().Be(Path.DirectorySeparatorChar.ToString());
|
||||
|
||||
provider.CanCreateFolder.Should().BeTrue();
|
||||
provider.Created.Should().Be(_model.Created);
|
||||
provider.Name.Should().Be("Yandex");
|
||||
provider.Id.Should().Be(_model.Id);
|
||||
|
||||
provider.SupportsDirectAuth.Should().BeFalse();
|
||||
provider.SupportsHostAuth.Should().BeFalse();
|
||||
provider.SupportsOAuth.Should().BeTrue();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ using Camelotia.Presentation.ViewModels;
|
|||
using Camelotia.Services.Interfaces;
|
||||
using Camelotia.Services.Providers;
|
||||
using Camelotia.Services.Storages;
|
||||
using Camelotia.Services.Models;
|
||||
using System.Reactive.Concurrency;
|
||||
using System.Collections.Generic;
|
||||
using ReactiveUI;
|
||||
|
@ -36,7 +37,7 @@ namespace Camelotia.Presentation.Uwp
|
|||
provider, current, main
|
||||
),
|
||||
new ProviderStorage(
|
||||
new Dictionary<string, Func<Guid, IProvider>>
|
||||
new Dictionary<string, Func<ProviderModel, IProvider>>
|
||||
{
|
||||
["Yandex Disk"] = id => new YandexDiskProvider(id, login, cache),
|
||||
["Vkontakte Docs"] = id => new VkDocsProvider(id, cache),
|
||||
|
|
|
@ -4,6 +4,7 @@ using Camelotia.Presentation.Xamarin.Droid.Services;
|
|||
using Camelotia.Services.Interfaces;
|
||||
using Camelotia.Services.Providers;
|
||||
using Camelotia.Services.Storages;
|
||||
using Camelotia.Services.Models;
|
||||
using System.Collections.Generic;
|
||||
using System.Reactive.Concurrency;
|
||||
using System.Reactive.Subjects;
|
||||
|
@ -70,7 +71,7 @@ namespace Camelotia.Presentation.Xamarin.Droid
|
|||
provider, current, main
|
||||
),
|
||||
new ProviderStorage(
|
||||
new Dictionary<string, Func<Guid, IProvider>>
|
||||
new Dictionary<string, Func<ProviderModel, IProvider>>
|
||||
{
|
||||
["Vkontakte Docs"] = id => new VkDocsProvider(id, cache),
|
||||
["Yandex Disk"] = id => new YandexDiskProvider(id, login, cache),
|
||||
|
|
|
@ -52,6 +52,8 @@ namespace Camelotia.Presentation.Interfaces
|
|||
string CurrentPath { get; }
|
||||
|
||||
string Description { get; }
|
||||
|
||||
DateTime Created { get; }
|
||||
|
||||
string Name { get; }
|
||||
|
||||
|
|
|
@ -18,17 +18,6 @@ namespace Camelotia.Presentation.ViewModels
|
|||
_file = file;
|
||||
}
|
||||
|
||||
public override int GetHashCode() => (Name, Path, IsFolder, Size).GetHashCode();
|
||||
|
||||
public override bool Equals(object instance)
|
||||
{
|
||||
return instance is FileViewModel file &&
|
||||
file.Name == Name &&
|
||||
file.Path == Path &&
|
||||
file.IsFolder == IsFolder &&
|
||||
file.Size == Size;
|
||||
}
|
||||
|
||||
public IProviderViewModel Provider { get; }
|
||||
|
||||
public string Modified => _file.Modified?.ToString(CultureInfo.InvariantCulture) ?? string.Empty;
|
||||
|
@ -42,5 +31,16 @@ namespace Camelotia.Presentation.ViewModels
|
|||
public string Name => _file.Name;
|
||||
|
||||
public string Path => _file.Path;
|
||||
|
||||
public override int GetHashCode() => (Name, Path, IsFolder, Size).GetHashCode();
|
||||
|
||||
public override bool Equals(object instance)
|
||||
{
|
||||
return instance is FileViewModel file &&
|
||||
file.Name == Name &&
|
||||
file.Path == Path &&
|
||||
file.IsFolder == IsFolder &&
|
||||
file.Size == Size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ using Camelotia.Services.Interfaces;
|
|||
using ReactiveUI.Fody.Helpers;
|
||||
using ReactiveUI;
|
||||
using DynamicData;
|
||||
using DynamicData.Binding;
|
||||
|
||||
namespace Camelotia.Presentation.ViewModels
|
||||
{
|
||||
|
@ -41,8 +42,9 @@ namespace Camelotia.Presentation.ViewModels
|
|||
storage.Refresh,
|
||||
outputScheduler: main);
|
||||
|
||||
var providers = storage.Providers();
|
||||
var providers = storage.Read();
|
||||
providers.Transform(x => providerFactory(x, files, authFactory(x)))
|
||||
.Sort(SortExpressionComparer<IProviderViewModel>.Descending(x => x.Created))
|
||||
.ObserveOn(RxApp.MainThreadScheduler)
|
||||
.StartWithEmpty()
|
||||
.Bind(out _providers)
|
||||
|
@ -60,7 +62,7 @@ namespace Camelotia.Presentation.ViewModels
|
|||
|
||||
providers.Where(changes => changes.Any())
|
||||
.ObserveOn(RxApp.MainThreadScheduler)
|
||||
.OnItemAdded(x => SelectedProvider = Providers.LastOrDefault())
|
||||
.OnItemAdded(x => SelectedProvider = Providers.FirstOrDefault())
|
||||
.OnItemRemoved(x => SelectedProvider = null)
|
||||
.Subscribe();
|
||||
|
||||
|
|
|
@ -267,6 +267,8 @@ namespace Camelotia.Presentation.ViewModels
|
|||
|
||||
public string CurrentPath => _currentPath?.Value ?? _provider.InitialPath;
|
||||
|
||||
public string Description => $"{_provider.Name} file system.";
|
||||
|
||||
public bool IsCurrentPathEmpty => _isCurrentPathEmpty.Value;
|
||||
|
||||
public ICommand DownloadSelectedFile => _downloadSelectedFile;
|
||||
|
@ -278,11 +280,11 @@ namespace Camelotia.Presentation.ViewModels
|
|||
public IEnumerable<IFileViewModel> Files => _files?.Value;
|
||||
|
||||
public bool CanInteract => _canInteract?.Value ?? true;
|
||||
|
||||
public string Description => _provider.Description;
|
||||
|
||||
public ICommand UnselectFile => _unselectFile;
|
||||
|
||||
public DateTime Created => _provider.Created;
|
||||
|
||||
public bool CanLogout => _canLogout.Value;
|
||||
|
||||
public bool IsLoading => _isLoading.Value;
|
||||
|
|
|
@ -14,7 +14,7 @@ namespace Camelotia.Services.Interfaces
|
|||
|
||||
string Name { get; }
|
||||
|
||||
string Description { get; }
|
||||
DateTime Created { get; }
|
||||
|
||||
string InitialPath { get; }
|
||||
|
||||
|
@ -24,6 +24,8 @@ namespace Camelotia.Services.Interfaces
|
|||
|
||||
Task DownloadFile(string from, Stream to);
|
||||
|
||||
bool CanCreateFolder { get; }
|
||||
|
||||
Task CreateFolder(string path, string name);
|
||||
|
||||
Task RenameFile(string path, string name);
|
||||
|
@ -33,16 +35,14 @@ namespace Camelotia.Services.Interfaces
|
|||
IObservable<bool> IsAuthorized { get; }
|
||||
|
||||
bool SupportsDirectAuth { get; }
|
||||
|
||||
|
||||
Task DirectAuth(string login, string password);
|
||||
|
||||
bool SupportsHostAuth { get; }
|
||||
|
||||
bool SupportsOAuth { get; }
|
||||
|
||||
bool CanCreateFolder { get; }
|
||||
|
||||
Task HostAuth(string address, int port, string login, string password);
|
||||
|
||||
Task DirectAuth(string login, string password);
|
||||
bool SupportsOAuth { get; }
|
||||
|
||||
Task OAuth();
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ namespace Camelotia.Services.Interfaces
|
|||
{
|
||||
IEnumerable<string> SupportedTypes { get; }
|
||||
|
||||
IObservable<IChangeSet<IProvider, Guid>> Providers();
|
||||
IObservable<IChangeSet<IProvider, Guid>> Read();
|
||||
|
||||
Task Add(string type);
|
||||
|
||||
|
|
|
@ -7,9 +7,11 @@ namespace Camelotia.Services.Models
|
|||
public Guid Id { get; set; }
|
||||
|
||||
public string Type { get; set; }
|
||||
|
||||
public string Token { get; set; }
|
||||
|
||||
|
||||
public string User { get; set; }
|
||||
|
||||
public string Token { get; set; }
|
||||
|
||||
public DateTime Created { get; set; }
|
||||
}
|
||||
}
|
|
@ -13,23 +13,24 @@ namespace Camelotia.Services.Providers
|
|||
public sealed class FtpProvider : IProvider
|
||||
{
|
||||
private readonly ISubject<bool> _isAuthorized = new ReplaySubject<bool>();
|
||||
private readonly ProviderModel _model;
|
||||
private Func<FtpClient> _factory;
|
||||
|
||||
public FtpProvider(Guid id)
|
||||
public FtpProvider(ProviderModel model)
|
||||
{
|
||||
Id = id;
|
||||
_model = model;
|
||||
_isAuthorized.OnNext(false);
|
||||
}
|
||||
|
||||
public Guid Id { get; }
|
||||
|
||||
public long? Size => null;
|
||||
|
||||
public string Name => "FTP";
|
||||
public Guid Id => _model.Id;
|
||||
|
||||
public string InitialPath => "/";
|
||||
|
||||
public string Description => "FTP remote file system.";
|
||||
public string Name => _model.Type;
|
||||
|
||||
public DateTime Created => _model.Created;
|
||||
|
||||
public IObservable<bool> IsAuthorized => _isAuthorized;
|
||||
|
||||
|
|
|
@ -6,9 +6,9 @@ using System.Net.Http;
|
|||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Threading.Tasks;
|
||||
using Akavache;
|
||||
using Camelotia.Services.Interfaces;
|
||||
using Camelotia.Services.Models;
|
||||
using Akavache;
|
||||
using Octokit;
|
||||
|
||||
namespace Camelotia.Services.Providers
|
||||
|
@ -20,25 +20,26 @@ namespace Camelotia.Services.Providers
|
|||
private readonly ISubject<bool> _isAuthenticated = new ReplaySubject<bool>(1);
|
||||
private readonly HttpClient _httpClient = new HttpClient();
|
||||
private readonly IBlobCache _blobCache;
|
||||
private readonly ProviderModel _model;
|
||||
private string _currentUserName;
|
||||
|
||||
public GitHubProvider(Guid id, IBlobCache blobCache)
|
||||
public GitHubProvider(ProviderModel model, IBlobCache blobCache)
|
||||
{
|
||||
Id = id;
|
||||
_model = model;
|
||||
_blobCache = blobCache;
|
||||
_isAuthenticated.OnNext(false);
|
||||
EnsureLoggedInIfTokenSaved();
|
||||
}
|
||||
|
||||
public Guid Id { get; }
|
||||
|
||||
public long? Size { get; } = null;
|
||||
public long? Size => null;
|
||||
|
||||
public string Name { get; } = "GitHub";
|
||||
public Guid Id => _model.Id;
|
||||
|
||||
public string Description { get; } = "GitHub repositories provider.";
|
||||
public string Name => _model.Type;
|
||||
|
||||
public string InitialPath { get; } = string.Empty;
|
||||
public DateTime Created => _model.Created;
|
||||
|
||||
public string InitialPath => string.Empty;
|
||||
|
||||
public IObservable<bool> IsAuthorized => _isAuthenticated;
|
||||
|
||||
|
|
|
@ -27,25 +27,26 @@ namespace Camelotia.Services.Providers
|
|||
|
||||
private readonly ISubject<bool> _isAuthorized = new ReplaySubject<bool>(1);
|
||||
private readonly IBlobCache _blobCache;
|
||||
private readonly ProviderModel _model;
|
||||
private DriveService _driveService;
|
||||
|
||||
public GoogleDriveProvider(Guid id, IBlobCache blobCache)
|
||||
public GoogleDriveProvider(ProviderModel model, IBlobCache blobCache)
|
||||
{
|
||||
Id = id;
|
||||
_model = model;
|
||||
_blobCache = blobCache;
|
||||
_isAuthorized.OnNext(false);
|
||||
EnsureLoggedInIfTokenSaved();
|
||||
}
|
||||
|
||||
public Guid Id { get; }
|
||||
public long? Size => null;
|
||||
|
||||
public long? Size { get; } = null;
|
||||
public Guid Id => _model.Id;
|
||||
|
||||
public string Name { get; } = "Google Drive";
|
||||
public string Name => _model.Type;
|
||||
|
||||
public string Description { get; } = "Google Drive file system.";
|
||||
public DateTime Created => _model.Created;
|
||||
|
||||
public string InitialPath { get; } = "/";
|
||||
public string InitialPath => "/";
|
||||
|
||||
public IObservable<bool> IsAuthorized => _isAuthorized;
|
||||
|
||||
|
|
|
@ -11,16 +11,18 @@ namespace Camelotia.Services.Providers
|
|||
{
|
||||
public sealed class LocalProvider : IProvider
|
||||
{
|
||||
public LocalProvider(Guid id) => Id = id;
|
||||
|
||||
public Guid Id { get; }
|
||||
|
||||
private readonly ProviderModel _model;
|
||||
|
||||
public LocalProvider(ProviderModel model) => _model = model;
|
||||
|
||||
public Guid Id => _model.Id;
|
||||
|
||||
public string Name => _model.Type;
|
||||
|
||||
public DateTime Created => _model.Created;
|
||||
|
||||
public long? Size => GetSizeOnAllDisks();
|
||||
|
||||
public string Name => "Local File System";
|
||||
|
||||
public string Description => "Provides access to files stored locally.";
|
||||
|
||||
public IObservable<bool> IsAuthorized { get; } = Observable.Return(true);
|
||||
|
||||
public bool SupportsDirectAuth => false;
|
||||
|
|
|
@ -13,22 +13,23 @@ namespace Camelotia.Services.Providers
|
|||
public sealed class SftpProvider : IProvider
|
||||
{
|
||||
private readonly ISubject<bool> _isAuthorized = new ReplaySubject<bool>();
|
||||
private readonly ProviderModel _model;
|
||||
private Func<SftpClient> _factory;
|
||||
|
||||
public SftpProvider(Guid id)
|
||||
public SftpProvider(ProviderModel model)
|
||||
{
|
||||
Id = id;
|
||||
_model = model;
|
||||
_isAuthorized.OnNext(false);
|
||||
}
|
||||
|
||||
public Guid Id { get; }
|
||||
|
||||
public long? Size => null;
|
||||
|
||||
public string Name => "SFTP";
|
||||
|
||||
public string Description => "Secure FTP file provider.";
|
||||
|
||||
public Guid Id => _model.Id;
|
||||
|
||||
public string Name => _model.Type;
|
||||
|
||||
public DateTime Created => _model.Created;
|
||||
|
||||
public string InitialPath => Path.DirectorySeparatorChar.ToString();
|
||||
|
||||
public IObservable<bool> IsAuthorized => _isAuthorized;
|
||||
|
|
|
@ -23,24 +23,25 @@ namespace Camelotia.Services.Providers
|
|||
public sealed class VkDocsProvider : IProvider
|
||||
{
|
||||
private readonly ReplaySubject<bool> _isAuthorized = new ReplaySubject<bool>();
|
||||
private readonly ProviderModel _model;
|
||||
private readonly IBlobCache _blobCache;
|
||||
private IVkApi _api = new VkApi();
|
||||
|
||||
public VkDocsProvider(Guid id, IBlobCache blobCache)
|
||||
public VkDocsProvider(ProviderModel model, IBlobCache blobCache)
|
||||
{
|
||||
Id = id;
|
||||
_model = model;
|
||||
_blobCache = blobCache;
|
||||
_isAuthorized.OnNext(false);
|
||||
EnsureLoggedInIfTokenSaved();
|
||||
}
|
||||
|
||||
public Guid Id { get; }
|
||||
|
||||
public long? Size => null;
|
||||
|
||||
public string Name => "Vkontakte Documents";
|
||||
public Guid Id => _model.Id;
|
||||
|
||||
public string Description => "Vkontakte documents provider.";
|
||||
public string Name => _model.Type;
|
||||
|
||||
public DateTime Created => _model.Created;
|
||||
|
||||
public IObservable<bool> IsAuthorized => _isAuthorized;
|
||||
|
||||
|
|
|
@ -8,10 +8,10 @@ using System.Net.Http.Headers;
|
|||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Threading.Tasks;
|
||||
using Akavache;
|
||||
using Camelotia.Services.Interfaces;
|
||||
using Camelotia.Services.Models;
|
||||
using Newtonsoft.Json;
|
||||
using Akavache;
|
||||
|
||||
namespace Camelotia.Services.Providers
|
||||
{
|
||||
|
@ -30,23 +30,24 @@ namespace Camelotia.Services.Providers
|
|||
private readonly HttpClient _http = new HttpClient();
|
||||
private readonly IAuthenticator _authenticator;
|
||||
private readonly IBlobCache _blobCache;
|
||||
private readonly ProviderModel _model;
|
||||
|
||||
public YandexDiskProvider(Guid id, IAuthenticator authenticator, IBlobCache blobCache)
|
||||
public YandexDiskProvider(ProviderModel model, IAuthenticator authenticator, IBlobCache blobCache)
|
||||
{
|
||||
Id = id;
|
||||
_model = model;
|
||||
_blobCache = blobCache;
|
||||
_authenticator = authenticator;
|
||||
_isAuthorized.OnNext(false);
|
||||
EnsureLoggedInIfTokenSaved();
|
||||
}
|
||||
|
||||
public Guid Id { get; }
|
||||
|
||||
public long? Size => null;
|
||||
|
||||
public string Name => "Yandex Disk";
|
||||
|
||||
public string Description => "Yandex Disk file provider.";
|
||||
public Guid Id => _model.Id;
|
||||
|
||||
public string Name => _model.Type;
|
||||
|
||||
public DateTime Created => _model.Created;
|
||||
|
||||
public IObservable<bool> IsAuthorized => _isAuthorized;
|
||||
|
||||
|
|
|
@ -1,23 +1,24 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Concurrency;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Akavache;
|
||||
using Camelotia.Services.Interfaces;
|
||||
using Camelotia.Services.Models;
|
||||
using DynamicData;
|
||||
using Akavache;
|
||||
|
||||
namespace Camelotia.Services.Storages
|
||||
{
|
||||
public sealed class ProviderStorage : IProviderStorage
|
||||
{
|
||||
private readonly SourceCache<IProvider, Guid> _connectable = new SourceCache<IProvider, Guid>(x => x.Id);
|
||||
private readonly IDictionary<string, Func<Guid, IProvider>> _factories;
|
||||
private readonly IDictionary<string, Func<ProviderModel, IProvider>> _factories;
|
||||
private readonly IBlobCache _blobCache;
|
||||
|
||||
public ProviderStorage(
|
||||
IDictionary<string, Func<Guid, IProvider>> factories,
|
||||
IDictionary<string, Func<ProviderModel, IProvider>> factories,
|
||||
IBlobCache blobCache)
|
||||
{
|
||||
_blobCache = blobCache;
|
||||
|
@ -26,46 +27,49 @@ namespace Camelotia.Services.Storages
|
|||
|
||||
public IEnumerable<string> SupportedTypes => _factories.Keys;
|
||||
|
||||
public IObservable<IChangeSet<IProvider, Guid>> Providers() => _connectable.Connect();
|
||||
public IObservable<IChangeSet<IProvider, Guid>> Read() => _connectable.Connect();
|
||||
|
||||
public Task Add(string typeName) => Task.Run(async () =>
|
||||
public Task Add(string typeName) => Task.Run(() =>
|
||||
{
|
||||
var type = _factories.Keys.First(x => x == typeName);
|
||||
var providerFactory = _factories[type];
|
||||
|
||||
var guid = Guid.NewGuid();
|
||||
var provider = providerFactory(guid);
|
||||
|
||||
var persistentId = guid.ToString();
|
||||
await _blobCache.InsertObject(persistentId, new ProviderModel
|
||||
var type = _factories.Keys.First(x => x == typeName);
|
||||
var model = new ProviderModel
|
||||
{
|
||||
Id = guid,
|
||||
Type = type,
|
||||
Token = null
|
||||
});
|
||||
|
||||
Token = null,
|
||||
Created = DateTime.Now
|
||||
};
|
||||
|
||||
_blobCache.InsertObject(guid.ToString(), model).Subscribe();
|
||||
var provider = _factories[type](model);
|
||||
_connectable.AddOrUpdate(provider);
|
||||
});
|
||||
|
||||
public Task Remove(Guid id) => Task.Run(async () =>
|
||||
public Task Remove(Guid id) => Task.Run(() =>
|
||||
{
|
||||
var persistentId = id.ToString();
|
||||
await _blobCache.InvalidateObject<ProviderModel>(persistentId);
|
||||
|
||||
_blobCache.InvalidateObject<ProviderModel>(persistentId).Subscribe();
|
||||
var provider = _connectable.Items.First(x => x.Id == id);
|
||||
_connectable.Remove(provider);
|
||||
});
|
||||
|
||||
public Task Refresh() => Task.Run(async () =>
|
||||
public async Task Refresh()
|
||||
{
|
||||
_connectable.Clear();
|
||||
var models = await _blobCache.GetAllObjects<ProviderModel>();
|
||||
var models = await _blobCache
|
||||
.GetAllObjects<ProviderModel>()
|
||||
.SubscribeOn(Scheduler.Default);
|
||||
|
||||
var providers = models
|
||||
.Where(model => model != null && _factories.ContainsKey(model.Type))
|
||||
.Select(model => _factories[model.Type](model.Id));
|
||||
.Select(model => _factories[model.Type](model));
|
||||
|
||||
foreach (var provider in providers)
|
||||
_connectable.AddOrUpdate(provider);
|
||||
});
|
||||
_connectable.Edit(cache =>
|
||||
{
|
||||
foreach (var provider in providers)
|
||||
cache.AddOrUpdate(provider);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,7 +23,7 @@ On Windows, double-click the `./run.bat` file.
|
|||
|
||||
You can compile Universal Windows Platform Camelotia app only on latest Windows 10. Make sure you have latest [Microsoft Visual Studio](https://visualstudio.microsoft.com/) installed. Make sure the "Universal Application Development" section is checked in [Visual Studio Installer](https://visualstudio.microsoft.com/ru/vs/).
|
||||
|
||||
<img src="assets/UiWindows1.jpg" width="550">
|
||||
<img src="assets/UiWindows1.png" width="600">
|
||||
|
||||
Supports light and dark themes!
|
||||
|
||||
|
|
Двоичные данные
assets/UiWindows1.jpg
Двоичные данные
assets/UiWindows1.jpg
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 153 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 608 KiB |
Загрузка…
Ссылка в новой задаче