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:
Artyom 2019-06-02 00:22:46 +03:00 коммит произвёл GitHub
Родитель 7fb5ab81bd
Коммит 768933e172
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
29 изменённых файлов: 253 добавлений и 141 удалений

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

@ -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

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 153 KiB

Двоичные данные
assets/UiWindows1.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 608 KiB