From 847a21f2563e0761ab4b636aeedd542d4c4c891e Mon Sep 17 00:00:00 2001 From: artyom Date: Thu, 20 Dec 2018 11:58:00 +0300 Subject: [PATCH] Implement UploadFile() for Yandex, move models into their own files --- .../LocalFileSystemProviderTests.cs | 6 +- .../ProviderViewModelTests.cs | 16 ++++ .../VkontakteFileSystemProviderTests.cs | 5 +- .../YandexFileSystemProviderTests.cs | 5 +- .../ViewModels/ProviderViewModel.cs | 8 +- Camelotia.Services/Models/FileModel.cs | 1 + .../Yandex/YandexContentItemResponse.cs | 23 ++++++ .../Yandex/YandexContentItemsResponse.cs | 11 +++ .../Models/Yandex/YandexContentResponse.cs | 10 +++ .../Models/Yandex/YandexFileLoadResponse.cs | 10 +++ .../Models/Yandex/YandexTokenAuthResponse.cs | 10 +++ .../Providers/LocalFileSystemProvider.cs | 21 ++---- .../Providers/VkontakteFileSystemProvider.cs | 9 --- .../Providers/YandexFileSystemProvider.cs | 73 ++++++------------- 14 files changed, 118 insertions(+), 90 deletions(-) create mode 100644 Camelotia.Services/Models/Yandex/YandexContentItemResponse.cs create mode 100644 Camelotia.Services/Models/Yandex/YandexContentItemsResponse.cs create mode 100644 Camelotia.Services/Models/Yandex/YandexContentResponse.cs create mode 100644 Camelotia.Services/Models/Yandex/YandexFileLoadResponse.cs create mode 100644 Camelotia.Services/Models/Yandex/YandexTokenAuthResponse.cs diff --git a/Camelotia.Presentation.Tests/LocalFileSystemProviderTests.cs b/Camelotia.Presentation.Tests/LocalFileSystemProviderTests.cs index be14aa6..bf9b0e4 100644 --- a/Camelotia.Presentation.Tests/LocalFileSystemProviderTests.cs +++ b/Camelotia.Presentation.Tests/LocalFileSystemProviderTests.cs @@ -53,10 +53,6 @@ namespace Camelotia.Presentation.Tests } [Fact] - public void ShouldImplementNonNullInitialPath() - { - _provider.InitialPath.Should().NotBeNull(); - } - + public void ShouldImplementNonNullInitialPath() => _provider.InitialPath.Should().NotBeNull(); } } \ No newline at end of file diff --git a/Camelotia.Presentation.Tests/ProviderViewModelTests.cs b/Camelotia.Presentation.Tests/ProviderViewModelTests.cs index a61d4a7..c56fc9b 100644 --- a/Camelotia.Presentation.Tests/ProviderViewModelTests.cs +++ b/Camelotia.Presentation.Tests/ProviderViewModelTests.cs @@ -122,6 +122,22 @@ namespace Camelotia.Presentation.Tests } }); + [Fact] + public void ShouldRefreshContentOfCurrentPathWhenFileIsUploaded() => new TestScheduler().With(scheduler => + { + _provider.InitialPath.Returns(Separator); + _fileManager.OpenRead().Returns(("example", Stream.Null)); + _authViewModel.IsAuthenticated.Returns(true); + + var model = BuildProviderViewModel(scheduler); + model.CurrentPath.Should().Be(Separator); + model.UploadToCurrentPath.CanExecute(null).Should().BeTrue(); + model.UploadToCurrentPath.Execute(null); + + scheduler.AdvanceBy(2); + _provider.Received(1).Get(Separator); + }); + private ProviderViewModel BuildProviderViewModel(IScheduler scheduler) { return new ProviderViewModel(_authViewModel, _fileManager, scheduler, scheduler, _provider); diff --git a/Camelotia.Presentation.Tests/VkontakteFileSystemProviderTests.cs b/Camelotia.Presentation.Tests/VkontakteFileSystemProviderTests.cs index fa7a72a..ea2bcda 100644 --- a/Camelotia.Presentation.Tests/VkontakteFileSystemProviderTests.cs +++ b/Camelotia.Presentation.Tests/VkontakteFileSystemProviderTests.cs @@ -10,9 +10,6 @@ namespace Camelotia.Presentation.Tests private readonly IProvider _provider = new VkontakteFileSystemProvider(); [Fact] - public void ShouldImplementNonNullInitialPath() - { - _provider.InitialPath.Should().NotBeNull(); - } + public void ShouldImplementNonNullInitialPath() => _provider.InitialPath.Should().NotBeNull(); } } \ No newline at end of file diff --git a/Camelotia.Presentation.Tests/YandexFileSystemProviderTests.cs b/Camelotia.Presentation.Tests/YandexFileSystemProviderTests.cs index 488ef93..ff720a6 100644 --- a/Camelotia.Presentation.Tests/YandexFileSystemProviderTests.cs +++ b/Camelotia.Presentation.Tests/YandexFileSystemProviderTests.cs @@ -10,9 +10,6 @@ namespace Camelotia.Presentation.Tests private readonly IProvider _provider = new YandexFileSystemProvider(); [Fact] - public void ShouldImplementNonNullInitialPath() - { - _provider.InitialPath.Should().NotBeNull(); - } + public void ShouldImplementNonNullInitialPath() => _provider.InitialPath.Should().NotBeNull(); } } \ No newline at end of file diff --git a/Camelotia.Presentation/ViewModels/ProviderViewModel.cs b/Camelotia.Presentation/ViewModels/ProviderViewModel.cs index 0b995d3..4ea9cb2 100644 --- a/Camelotia.Presentation/ViewModels/ProviderViewModel.cs +++ b/Camelotia.Presentation/ViewModels/ProviderViewModel.cs @@ -113,11 +113,14 @@ namespace Camelotia.Presentation.ViewModels _uploadToCurrentPath = ReactiveCommand.CreateFromObservable( () => Observable .FromAsync(fileManager.OpenRead) + .Where(response => response.Name != null && response.Stream != null) .Select(x => _provider.UploadFile(CurrentPath, x.Stream, x.Name)) .SelectMany(task => task.ToObservable()), canUploadToCurrentPath, mainThread); + _uploadToCurrentPath.InvokeCommand(_refresh); + var canDownloadSelectedFile = this .WhenAnyValue(x => x.SelectedFile) .Select(file => file != null && !file.IsFolder && !file.IsDrive) @@ -126,6 +129,7 @@ namespace Camelotia.Presentation.ViewModels _downloadSelectedFile = ReactiveCommand.CreateFromObservable( () => Observable .FromAsync(() => fileManager.OpenWrite(SelectedFile.Name)) + .Where(stream => stream != null) .Select(stream => _provider.DownloadFile(SelectedFile.Path, stream)) .SelectMany(task => task.ToObservable()), canDownloadSelectedFile, @@ -174,6 +178,8 @@ namespace Camelotia.Presentation.ViewModels [Reactive] public FileModel SelectedFile { get; set; } + public string CurrentPath => _currentPath?.Value ?? _provider.InitialPath; + public ICommand DownloadSelectedFile => _downloadSelectedFile; public ICommand UploadToCurrentPath => _uploadToCurrentPath; @@ -184,8 +190,6 @@ namespace Camelotia.Presentation.ViewModels public string Description => _provider.Description; - public string CurrentPath => _currentPath?.Value ?? _provider.InitialPath; - public bool CanLogout => _canLogout.Value; public bool IsLoading => _isLoading.Value; diff --git a/Camelotia.Services/Models/FileModel.cs b/Camelotia.Services/Models/FileModel.cs index 949d721..326069e 100644 --- a/Camelotia.Services/Models/FileModel.cs +++ b/Camelotia.Services/Models/FileModel.cs @@ -10,6 +10,7 @@ namespace Camelotia.Services.Models IsDrive = isDrive; Size = size; } + public string Name { get; } public string Path { get; } diff --git a/Camelotia.Services/Models/Yandex/YandexContentItemResponse.cs b/Camelotia.Services/Models/Yandex/YandexContentItemResponse.cs new file mode 100644 index 0000000..6f6206e --- /dev/null +++ b/Camelotia.Services/Models/Yandex/YandexContentItemResponse.cs @@ -0,0 +1,23 @@ +using System; +using Newtonsoft.Json; + +namespace Camelotia.Services.Models.Yandex +{ + internal class YandexContentItemResponse + { + [JsonProperty("path")] + public string Path { get; set; } + + [JsonProperty("type")] + public string Type { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("size")] + public long Size { get; set; } + + [JsonProperty("created")] + public DateTime Created { get; set; } + } +} \ No newline at end of file diff --git a/Camelotia.Services/Models/Yandex/YandexContentItemsResponse.cs b/Camelotia.Services/Models/Yandex/YandexContentItemsResponse.cs new file mode 100644 index 0000000..a069ba1 --- /dev/null +++ b/Camelotia.Services/Models/Yandex/YandexContentItemsResponse.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Camelotia.Services.Models.Yandex +{ + internal class YandexContentItemsResponse + { + [JsonProperty("items")] + public IList Items { get; set; } + } +} \ No newline at end of file diff --git a/Camelotia.Services/Models/Yandex/YandexContentResponse.cs b/Camelotia.Services/Models/Yandex/YandexContentResponse.cs new file mode 100644 index 0000000..74707c0 --- /dev/null +++ b/Camelotia.Services/Models/Yandex/YandexContentResponse.cs @@ -0,0 +1,10 @@ +using Newtonsoft.Json; + +namespace Camelotia.Services.Models.Yandex +{ + internal class YandexContentResponse + { + [JsonProperty("_embedded")] + public YandexContentItemsResponse Embedded { get; set; } + } +} \ No newline at end of file diff --git a/Camelotia.Services/Models/Yandex/YandexFileLoadResponse.cs b/Camelotia.Services/Models/Yandex/YandexFileLoadResponse.cs new file mode 100644 index 0000000..b619e38 --- /dev/null +++ b/Camelotia.Services/Models/Yandex/YandexFileLoadResponse.cs @@ -0,0 +1,10 @@ +using Newtonsoft.Json; + +namespace Camelotia.Services.Models.Yandex +{ + internal class YandexFileLoadResponse + { + [JsonProperty("href")] + public string Href { get; set; } + } +} \ No newline at end of file diff --git a/Camelotia.Services/Models/Yandex/YandexTokenAuthResponse.cs b/Camelotia.Services/Models/Yandex/YandexTokenAuthResponse.cs new file mode 100644 index 0000000..d77de4a --- /dev/null +++ b/Camelotia.Services/Models/Yandex/YandexTokenAuthResponse.cs @@ -0,0 +1,10 @@ +using Newtonsoft.Json; + +namespace Camelotia.Services.Models.Yandex +{ + internal class YandexTokenAuthResponse + { + [JsonProperty("access_token")] + public string AccessToken { get; set; } + } +} \ No newline at end of file diff --git a/Camelotia.Services/Providers/LocalFileSystemProvider.cs b/Camelotia.Services/Providers/LocalFileSystemProvider.cs index 1991d20..54e8f31 100644 --- a/Camelotia.Services/Providers/LocalFileSystemProvider.cs +++ b/Camelotia.Services/Providers/LocalFileSystemProvider.cs @@ -57,35 +57,27 @@ namespace Camelotia.Services.Providers .AsEnumerable(); }); - public Task DownloadFile(string from, Stream to) + public async Task DownloadFile(string from, Stream to) { - if (from == null) throw new ArgumentNullException(nameof(from)); - if (to == null) throw new ArgumentNullException(nameof(to)); if (IsDirectory(from)) throw new InvalidOperationException("Can't download directory."); - + using (var fileStream = File.OpenRead(from)) { fileStream.Seek(0, SeekOrigin.Begin); - fileStream.CopyTo(to); + await fileStream.CopyToAsync(to); } - - return Task.CompletedTask; } - public Task UploadFile(string to, Stream from, string name) + public async Task UploadFile(string to, Stream from, string name) { - if (to == null) throw new ArgumentNullException(nameof(to)); - if (from == null) throw new ArgumentNullException(nameof(from)); if (!IsDirectory(to)) throw new InvalidOperationException("Can't upload to a non-directory."); - + var path = Path.Combine(to, name); using (var fileStream = File.Create(path)) { from.Seek(0, SeekOrigin.Begin); - from.CopyTo(fileStream); + await from.CopyToAsync(fileStream); } - - return Task.CompletedTask; } private static string GetSizeOnAllDisks() @@ -109,7 +101,6 @@ namespace Camelotia.Services.Providers private static bool IsDirectory(string path) { var attributes = File.GetAttributes(path); - return attributes.HasFlag(FileAttributes.Directory); } } diff --git a/Camelotia.Services/Providers/VkontakteFileSystemProvider.cs b/Camelotia.Services/Providers/VkontakteFileSystemProvider.cs index e3172f1..1f296b2 100644 --- a/Camelotia.Services/Providers/VkontakteFileSystemProvider.cs +++ b/Camelotia.Services/Providers/VkontakteFileSystemProvider.cs @@ -68,8 +68,6 @@ namespace Camelotia.Services.Providers public async Task> Get(string path) { - if (path == null) throw new ArgumentNullException(nameof(path)); - var documents = await _api.Docs.GetAsync(); return documents.Select(document => { @@ -82,9 +80,6 @@ namespace Camelotia.Services.Providers public async Task DownloadFile(string from, Stream to) { - if (from == null) throw new ArgumentNullException(nameof(from)); - if (to == null) throw new ArgumentNullException(nameof(to)); - var isValidUriString = Uri.IsWellFormedUriString(from, UriKind.Absolute); if (!isValidUriString) throw new InvalidOperationException("Uri is invalid."); @@ -96,10 +91,6 @@ namespace Camelotia.Services.Providers public async Task UploadFile(string to, Stream from, string name) { - if (to == null) throw new ArgumentNullException(nameof(to)); - if (from == null) throw new ArgumentNullException(nameof(from)); - if (name == null) throw new ArgumentNullException(nameof(name)); - var server = await _api.Docs.GetUploadServerAsync().ConfigureAwait(false); var uri = new Uri(server.UploadUrl); diff --git a/Camelotia.Services/Providers/YandexFileSystemProvider.cs b/Camelotia.Services/Providers/YandexFileSystemProvider.cs index 3817b02..099f228 100644 --- a/Camelotia.Services/Providers/YandexFileSystemProvider.cs +++ b/Camelotia.Services/Providers/YandexFileSystemProvider.cs @@ -11,6 +11,7 @@ using System.Text; using System.Threading.Tasks; using Camelotia.Services.Interfaces; using Camelotia.Services.Models; +using Camelotia.Services.Models.Yandex; using Newtonsoft.Json; namespace Camelotia.Services.Providers @@ -19,6 +20,7 @@ namespace Camelotia.Services.Providers { private const string YandexAuthTokenUrl = "https://oauth.yandex.ru/token"; private const string CloudApiDownloadFileUrl = "https://cloud-api.yandex.net/v1/disk/resources/download?path="; + private const string CloudApiUploadFileUrl = "https://cloud-api.yandex.net/v1/disk/resources/upload?path="; private const string CloudApiGetPathBase = "https://cloud-api.yandex.net:443/v1/disk/resources?path="; private const string SuccessContent = "Please return to the app."; private const string ClientSecret = "f14bfc0275a34ceea83d7de7f4b50898"; @@ -45,8 +47,6 @@ namespace Camelotia.Services.Providers public async Task> Get(string path) { - if (string.IsNullOrWhiteSpace(path)) throw new ArgumentException(nameof(path)); - var yaPath = path.Replace("\\", "/"); var encodedPath = WebUtility.UrlEncode(yaPath); var pathUrl = CloudApiGetPathBase + encodedPath; @@ -70,24 +70,37 @@ namespace Camelotia.Services.Providers public async Task DownloadFile(string from, Stream to) { - if (from == null) throw new ArgumentNullException(nameof(from)); - if (to == null) throw new ArgumentNullException(nameof(to)); - - var encodedPath = WebUtility.UrlEncode(from); + var yaPath = from.Replace("\\", "/"); + var encodedPath = WebUtility.UrlEncode(yaPath); var pathUrl = CloudApiDownloadFileUrl + encodedPath; using (var response = await _http.GetAsync(pathUrl).ConfigureAwait(false)) { - var json = await response.Content.ReadAsStringAsync(); response.EnsureSuccessStatusCode(); + var json = await response.Content.ReadAsStringAsync(); + var content = JsonConvert.DeserializeObject(json); - var content = JsonConvert.DeserializeObject(json); using (var file = await _http.GetAsync(content.Href).ConfigureAwait(false)) using (var stream = await file.Content.ReadAsStreamAsync().ConfigureAwait(false)) await stream.CopyToAsync(to).ConfigureAwait(false); } } - public Task UploadFile(string to, Stream from, string name) => Task.CompletedTask; + public async Task UploadFile(string to, Stream from, string name) + { + var yaPath = Path.Combine(to, name).Replace("\\", "/"); + var encodedPath = WebUtility.UrlEncode(yaPath); + var pathUrl = CloudApiUploadFileUrl + encodedPath; + using (var response = await _http.GetAsync(pathUrl).ConfigureAwait(false)) + { + response.EnsureSuccessStatusCode(); + var json = await response.Content.ReadAsStringAsync(); + var content = JsonConvert.DeserializeObject(json); + + var httpContent = new StreamContent(from); + using (var file = await _http.PutAsync(content.Href, httpContent).ConfigureAwait(false)) + file.EnsureSuccessStatusCode(); + } + } public Task Logout() { @@ -159,47 +172,5 @@ namespace Camelotia.Services.Providers return "https://oauth.yandex.ru/authorize?response_type=code" + $"&client_id={ClientId}&redirect_url={redirect}"; } - - private class YandexTokenAuthResponse - { - [JsonProperty("access_token")] - public string AccessToken { get; set; } - } - - private class YandexContentResponse - { - [JsonProperty("_embedded")] - public YandexContentItemsResponse Embedded { get; set; } - } - - private class YandexContentItemsResponse - { - [JsonProperty("items")] - public IList Items { get; set; } - } - - private class YandexContentItemResponse - { - [JsonProperty("path")] - public string Path { get; set; } - - [JsonProperty("type")] - public string Type { get; set; } - - [JsonProperty("name")] - public string Name { get; set; } - - [JsonProperty("size")] - public long Size { get; set; } - - [JsonProperty("created")] - public DateTime Created { get; set; } - } - - private class YandexFileDownloadResponse - { - [JsonProperty("href")] - public string Href { get; set; } - } } } \ No newline at end of file