* Initial Google Drive support
* Persistent GitHub auth state
* Fix uwp compilation
* Fix xf compilation
* Download and upload functionality
This commit is contained in:
Artyom 2019-03-17 21:23:02 +03:00 коммит произвёл GitHub
Родитель 9a38ed0997
Коммит b6a397280b
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
12 изменённых файлов: 225 добавлений и 33 удалений

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

@ -58,7 +58,8 @@ namespace Camelotia.Presentation.Avalonia
["Yandex Disk"] = id => new YandexFileSystemProvider(id, login, cache),
["FTP"] = id => new FtpFileSystemProvider(id),
["SFTP"] = id => new SftpFileSystemProvider(id),
["GitHub"] = id => new GitHubFileSystemProvider(id)
["GitHub"] = id => new GitHubFileSystemProvider(id, cache),
["Google Drive"] = id => new GoogleDriveFileSystemProvider(id, cache)
},
cache
),

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

@ -43,6 +43,7 @@
SelectionMode="Toggle"
Items="{Binding Files}"
IsVisible="{Binding IsReady}"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
SelectedItem="{Binding SelectedFile, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate DataType="models:FileModel">

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

@ -46,7 +46,7 @@ namespace Camelotia.Presentation.Uwp
["Vkontakte Docs"] = id => new VkontakteFileSystemProvider(id, cache),
["FTP"] = id => new FtpFileSystemProvider(id),
["SFTP"] = id => new SftpFileSystemProvider(id),
["GitHub"] = id => new GitHubFileSystemProvider(id)
["GitHub"] = id => new GitHubFileSystemProvider(id, cache)
},
cache
),

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

@ -80,7 +80,7 @@ namespace Camelotia.Presentation.Xamarin.Droid
["Yandex Disk"] = id => new YandexFileSystemProvider(id, login, cache),
["FTP"] = id => new FtpFileSystemProvider(id),
["SFTP"] = id => new SftpFileSystemProvider(id),
["GitHub"] = id => new GitHubFileSystemProvider(id)
["GitHub"] = id => new GitHubFileSystemProvider(id, cache)
},
cache
),

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

@ -8,6 +8,7 @@
<PackageReference Include="DynamicData" Version="6.7.1.2534" />
<PackageReference Include="akavache" Version="6.2.3" />
<PackageReference Include="FluentFTP" Version="19.2.2" />
<PackageReference Include="Google.Apis.Drive.v3" Version="1.38.0.1530" />
<PackageReference Include="Octokit" Version="0.32.0" />
<PackageReference Include="ssh.net" Version="2016.1.0" />
<PackageReference Include="System.Reactive" Version="4.1.2" />

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

@ -1,4 +1,5 @@
using System;
using System.Linq;
namespace Camelotia.Services.Models
{
@ -18,20 +19,19 @@ namespace Camelotia.Services.Models
public FileModel(string name, string path, bool isFolder, string size, DateTime? modified = null)
{
Name = name;
Path = path;
Size = size;
IsFolder = isFolder;
Modified = modified?.ToString();
Name = new string(name.Take(40).ToArray());
}
public override int GetHashCode() => (Name, Path, IsFolder, Size).GetHashCode();
public override bool Equals(object obj)
{
var file = obj as FileModel;
return
file != null &&
obj is FileModel file &&
file.Name == Name &&
file.Path == Path &&
file.IsFolder == IsFolder &&

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

@ -9,5 +9,7 @@ namespace Camelotia.Services.Models
public string Type { get; set; }
public string Token { get; set; }
public string User { get; set; }
}
}

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

@ -3,9 +3,11 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Security.Cryptography;
using System.Threading.Tasks;
using Akavache;
using Camelotia.Services.Interfaces;
using Camelotia.Services.Models;
using Octokit;
@ -18,23 +20,26 @@ namespace Camelotia.Services.Providers
private readonly GitHubClient _gitHub = new GitHubClient(new ProductHeaderValue(GithubApplicationId));
private readonly ISubject<bool> _isAuthenticated = new ReplaySubject<bool>(1);
private readonly HttpClient _httpClient = new HttpClient();
private readonly IBlobCache _blobCache;
private string _currentUserName;
public GitHubFileSystemProvider(Guid id)
public GitHubFileSystemProvider(Guid id, IBlobCache blobCache)
{
Id = id;
_blobCache = blobCache;
_isAuthenticated.OnNext(false);
EnsureLoggedInIfTokenSaved();
}
public Guid Id { get; }
public string Size => "Unknown";
public string Size { get; } = "Unknown";
public string Name => "GitHub";
public string Name { get; } = "GitHub";
public string Description => "GitHub repositories provider.";
public string Description { get; } = "GitHub repositories provider.";
public string InitialPath => string.Empty;
public string InitialPath { get; } = string.Empty;
public IObservable<bool> IsAuthorized => _isAuthenticated;
@ -55,6 +60,13 @@ namespace Camelotia.Services.Providers
_currentUserName = login;
_gitHub.Credentials = new Credentials(login, password);
await _gitHub.User.Current();
var persistentId = Id.ToString();
var model = await _blobCache.GetObject<ProviderModel>(persistentId);
model.Token = password;
model.User = login;
await _blobCache.InsertObject(persistentId, model);
_isAuthenticated.OnNext(true);
}
@ -118,20 +130,23 @@ namespace Camelotia.Services.Providers
to.Close();
}
public Task CreateFolder(string path, string name)
{
throw new NotImplementedException();
}
public Task CreateFolder(string path, string name) => throw new NotImplementedException();
public Task RenameFile(FileModel file, string name)
{
throw new NotImplementedException();
}
public Task RenameFile(FileModel file, string name) => throw new NotImplementedException();
public Task UploadFile(string to, Stream from, string name) => throw new NotImplementedException();
public Task Delete(FileModel file) => throw new NotImplementedException();
private async void EnsureLoggedInIfTokenSaved()
{
var persistentId = Id.ToString();
var model = await _blobCache.GetOrFetchObject(persistentId, () => Task.FromResult(default(ProviderModel)));
if (model?.User == null || model?.Token == null) return;
_gitHub.Credentials = new Credentials(model.User, model.Token);
_isAuthenticated.OnNext(true);
}
private static (string Repository, string Path, string Separator) GetRepositoryNameAndFilePath(string input)
{
var separator = Path.DirectorySeparatorChar;
@ -145,15 +160,5 @@ namespace Camelotia.Services.Providers
var path = Path.Combine(pathParts);
return (repositoryName, path, separator.ToString());
}
private static string GetSha1Hash(Stream stream)
{
using (var sha1 = new SHA1CryptoServiceProvider())
{
var hash = sha1.ComputeHash(stream);
var hashStr = Convert.ToBase64String(hash);
return hashStr.TrimEnd('=');
}
}
}
}

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

@ -0,0 +1,183 @@
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading;
using System.Threading.Tasks;
using Akavache;
using Camelotia.Services.Interfaces;
using Camelotia.Services.Models;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Download;
using Google.Apis.Drive.v3;
using Google.Apis.Drive.v3.Data;
using Google.Apis.Services;
using Google.Apis.Util.Store;
using File = Google.Apis.Drive.v3.Data.File;
namespace Camelotia.Services.Providers
{
public sealed class GoogleDriveFileSystemProvider : IProvider
{
private const string GoogleDriveApplicationName = "Camelotia";
private const string GoogleDriveClientId = "1096201018044-qbv35mo5cd7b5utfjpg83v5lsuhssvvg.apps.googleusercontent.com";
private const string GoogleDriveClientSecret = "w4F099v9awUEAs66rmCxLbYr";
private const string GoogleDriveUserName = "user";
private readonly ISubject<bool> _isAuthorized = new ReplaySubject<bool>(1);
private readonly IBlobCache _blobCache;
private DriveService _driveService;
public GoogleDriveFileSystemProvider(Guid id, IBlobCache blobCache)
{
Id = id;
_blobCache = blobCache;
_isAuthorized.OnNext(false);
EnsureLoggedInIfTokenSaved();
}
public Guid Id { get; }
public string Size { get; } = "Unknown";
public string Name { get; } = "Google Drive";
public string Description { get; } = "Google Drive file system.";
public string InitialPath { get; } = "/";
public IObservable<bool> IsAuthorized => _isAuthorized;
public bool SupportsDirectAuth => false;
public bool SupportsHostAuth => false;
public bool SupportsOAuth => true;
public bool CanCreateFolder => false;
public async Task<IEnumerable<FileModel>> Get(string path)
{
var list = _driveService.Files.List();
list.PageSize = 1000;
list.Fields = "files(id, name, size, modifiedTime)";
var response = await list.ExecuteAsync().ConfigureAwait(false);
var files = from file in response.Files
let size = file.Size.GetValueOrDefault()
let bytes = ByteConverter.BytesToString(size)
select new FileModel(file.Name, file.Id, false, bytes, file.ModifiedTime);
return files;
}
public async Task UploadFile(string to, Stream from, string name)
{
var create = _driveService.Files.Create(new File {Name = name}, from, "application/vnd.google-apps.file");
await create.UploadAsync().ConfigureAwait(false);
}
public async Task DownloadFile(string from, Stream to)
{
var file = _driveService.Files.Get(from);
var progress = await file.DownloadAsync(to).ConfigureAwait(false);
while (progress.Status == DownloadStatus.Downloading)
await Task.Delay(1000);
}
public async Task RenameFile(FileModel file, string name)
{
var update = _driveService.Files.Update(new File {Name = name}, file.Path);
await update.ExecuteAsync().ConfigureAwait(false);
}
public async Task Delete(FileModel file)
{
var delete = _driveService.Files.Delete(file.Path);
await delete.ExecuteAsync().ConfigureAwait(false);
}
public Task CreateFolder(string path, string name) => Task.CompletedTask;
public Task HostAuth(string address, int port, string login, string password) => Task.CompletedTask;
public Task DirectAuth(string login, string password) => Task.CompletedTask;
public Task OAuth() => Task.Run(AuthenticateAsync);
public Task Logout() => Task.Run(async () =>
{
var keys = await _blobCache.GetAllKeys();
var googleDriveKeys = keys.Where(x => x.StartsWith("google-drive"));
foreach (var driveKey in googleDriveKeys)
await _blobCache.Invalidate(driveKey);
_driveService = null;
_isAuthorized.OnNext(false);
return Task.CompletedTask;
});
private void EnsureLoggedInIfTokenSaved() => Task.Run(async () =>
{
try
{
await AuthenticateAsync();
}
catch (Exception)
{
// ignore
}
});
private async Task AuthenticateAsync()
{
var credential = await GoogleWebAuthorizationBroker
.AuthorizeAsync(
new ClientSecrets {ClientId = GoogleDriveClientId, ClientSecret = GoogleDriveClientSecret},
new[] {DriveService.Scope.Drive},
GoogleDriveUserName,
CancellationToken.None,
new AkavacheDataStore(_blobCache))
.ConfigureAwait(false);
var initializer = new BaseClientService.Initializer
{
HttpClientInitializer = credential,
ApplicationName = GoogleDriveApplicationName
};
_driveService = new DriveService(initializer);
_isAuthorized.OnNext(true);
}
private sealed class AkavacheDataStore : IDataStore
{
private readonly IBlobCache _blobCache;
public AkavacheDataStore(IBlobCache blobCache) => _blobCache = blobCache;
public async Task StoreAsync<T>(string key, T value)
{
var identity = $"google-drive-{key}";
await _blobCache.InsertObject(identity, value);
}
public async Task DeleteAsync<T>(string key)
{
var identity = $"google-drive-{key}";
await _blobCache.Invalidate(identity);
}
public async Task<T> GetAsync<T>(string key)
{
var identity = $"google-drive-{key}";
var value = await _blobCache.GetOrFetchObject<T>(identity, () => Task.FromResult(default(T)));
return value;
}
public Task ClearAsync() => Task.CompletedTask;
}
}
}

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

@ -179,7 +179,7 @@ namespace Camelotia.Services.Providers
private async void EnsureLoggedInIfTokenSaved()
{
var persistentId = Id.ToString();
var model = await _blobCache.GetOrFetchObject(persistentId, () => Observable.Return<ProviderModel>(null));
var model = await _blobCache.GetOrFetchObject(persistentId, () => Task.FromResult(default(ProviderModel)));
var token = model?.Token;
if (string.IsNullOrWhiteSpace(token)) return;

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

@ -1,6 +1,6 @@
[![Build Status](https://worldbeater.visualstudio.com/Camelotia/_apis/build/status/Camelotia-CI)](https://worldbeater.visualstudio.com/Camelotia/_build/latest?definitionId=1) [![Pull Requests](https://img.shields.io/github/issues-pr/worldbeater/camelotia.svg)](https://github.com/worldbeater/Camelotia/pulls) [![Issues](https://img.shields.io/github/issues/worldbeater/camelotia.svg)](https://github.com/worldbeater/Camelotia/issues) ![License](https://img.shields.io/github/license/worldbeater/camelotia.svg) ![Size](https://img.shields.io/github/repo-size/worldbeater/camelotia.svg)
The app runs on Windows, Linux, MacOS, XBox, Surface Hub and HoloLens. Built with [ReactiveUI](https://github.com/reactiveui/ReactiveUI).
File manager for cloud storages. Supports Yandex Disk, Google Drive, VK Documents, GitHub, FTP, SFTP. The app runs on Windows, Linux, MacOS, XBox, Surface Hub and HoloLens. Built with [ReactiveUI](https://github.com/reactiveui/ReactiveUI).
## Compiling Avalonia app
@ -25,8 +25,6 @@ You can compile Universal Windows Platform Camelotia app only on latest Windows
Supports light and dark themes!
<img src="./UiWindows2.jpg" width="550">
## Compiling Xamarin Forms app
To compile the Xamarin Forms Android application, you need to install appropriate Android SDK v8.1. This can be achieved by using [Visual Studio Installer](https://visualstudio.microsoft.com/ru/vs/) and selecting "Mobile Development" section there.
@ -51,4 +49,5 @@ File system providers are located at `./Camelotia.Services/Providers/`. To add a
- <a href="https://github.com/robinrodricks/FluentFTP">FluentFTP</a> FTP implementation
- <a href="https://github.com/sshnet/SSH.NET/">SSH.NET</a> SFTP implementation
- <a href="https://github.com/vknet/vk">VkNet</a> Vkontakte SDK for .NET
- <a href="https://github.com/googleapis/google-api-dotnet-client">Google Drive</a> SDK for .NET
- <a href="https://www.jetbrains.com/rider/">JetBrains Rider</a> and <a href="https://visualstudio.microsoft.com/">Microsoft Visual Studio</a> IDEs

Двоичные данные
UiWindows2.jpg

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

До

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