this is the first attempt to fix the currently broken OneDrive implemenation by upgrading to Graph SDK 5. Unfortunately, the SDK is much harder to use because of a missing abstraction for DriveItems on different places (regular folder, special folder, shared folder) and also doesn't work well, see e.g. https://stackoverflow.com/questions/79374963/list-shared-folders-with-graph-sdk or https://stackoverflow.com/questions/79374845/graph-sdk-c-missing-methods-for-working-with-app-folder.
This commit is contained in:
@@ -1,25 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
using Android.Util;
|
||||
using Java.Net;
|
||||
using keepass2android.Io.ItemLocation;
|
||||
using KeePassLib.Serialization;
|
||||
using KeePassLib.Utility;
|
||||
using Microsoft.Graph;
|
||||
using Microsoft.Graph.Auth;
|
||||
using Microsoft.Graph.Drives.Item.Items.Item.CreateUploadSession;
|
||||
using Microsoft.Graph.Models;
|
||||
using Microsoft.Identity.Client;
|
||||
using Newtonsoft.Json;
|
||||
using Microsoft.Kiota.Abstractions;
|
||||
using Microsoft.Kiota.Abstractions.Authentication;
|
||||
|
||||
//there are several "DriveItemItemRequestBuilder" classes in different namespaces. Unfortunately they do not share a common interface anymore.
|
||||
//Prefix the aliases with Kp2a to indicate that they are non-official aliases.
|
||||
using Kp2aDriveItemRequestBuilder = Microsoft.Graph.Drives.Item.Items.Item.DriveItemItemRequestBuilder;
|
||||
using Kp2aSharedDriveItemRequestBuilder = Microsoft.Graph.Shares.Item.Items.Item.DriveItemItemRequestBuilder;
|
||||
using Kp2aSpecialDriveItemRequestBuilder = Microsoft.Graph.Drives.Item.Special.Item.DriveItemItemRequestBuilder;
|
||||
//NOTE: even though CustomDriveItemItemRequestBuilder derives from Kp2aDriveItemRequestBuilder, we cannot use polymorphism here because the methods are declared with "new".
|
||||
//If you cast assign an CustomDriveItemItemRequestBuilder object to a variable declared as Kp2aDriveItemRequestBuilder and then call a method on it, it will fail.
|
||||
|
||||
using Exception = System.Exception;
|
||||
using File = Microsoft.Graph.File;
|
||||
using String = System.String;
|
||||
|
||||
namespace keepass2android.Io
|
||||
@@ -202,7 +204,7 @@ namespace keepass2android.Io
|
||||
class PathItemBuilder
|
||||
{
|
||||
private readonly string _specialFolder;
|
||||
public IGraphServiceClient client;
|
||||
public GraphServiceClient client;
|
||||
public OneDrive2ItemLocation<OneDrive2PrefixContainerType> itemLocation;
|
||||
public bool verbose;
|
||||
|
||||
@@ -211,11 +213,175 @@ namespace keepass2android.Io
|
||||
_specialFolder = specialFolder;
|
||||
}
|
||||
|
||||
|
||||
public IDriveItemRequestBuilder getPathItem()
|
||||
/// <summary>
|
||||
/// Wraps the different DriveItemRequestBuilder classes and allows accessing the different types easily
|
||||
/// </summary>
|
||||
public class DriveItemRequests
|
||||
{
|
||||
Kp2aLog.Log("getPathItem for " + itemLocation.ToString());
|
||||
IDriveItemRequestBuilder pathItem;
|
||||
|
||||
public class DriveItemRequestsResult<T>
|
||||
{
|
||||
private readonly DriveItemRequests _req;
|
||||
|
||||
public DriveItemRequestsResult(DriveItemRequests req)
|
||||
{
|
||||
_req = req;
|
||||
}
|
||||
|
||||
public Task<T?> Result { get; set; }
|
||||
|
||||
|
||||
public DriveItemRequestsResult<T> ForSharedDriveItemRequests(Func<Kp2aSharedDriveItemRequestBuilder, Task<T?>> action)
|
||||
{
|
||||
if (_req.SharedDriveItemRequestBuilder != null)
|
||||
{
|
||||
Result = action(_req.SharedDriveItemRequestBuilder);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
public DriveItemRequestsResult<T> ForDriveItemRequests(Func<Kp2aDriveItemRequestBuilder, Task<T?>> action)
|
||||
{
|
||||
if (_req.DriveItemRequestBuilder != null)
|
||||
{
|
||||
Result = action(_req.DriveItemRequestBuilder);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public DriveItemRequestsResult<T> ForCustomDriveItemRequests(Func<CustomDriveItemItemRequestBuilder, Task<T?>> action)
|
||||
{
|
||||
if (_req.CustomDriveItemRequestBuilder != null)
|
||||
{
|
||||
Result = action(_req.CustomDriveItemRequestBuilder);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public DriveItemRequestsResult<T> ForSpecialDriveItemRequests(Func<Kp2aSpecialDriveItemRequestBuilder, Task<T?>> action)
|
||||
{
|
||||
if (_req.SpecialDriveItemRequestBuilder != null)
|
||||
{
|
||||
Result = action(_req.SpecialDriveItemRequestBuilder);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class DriveItemRequestsAsyncTask
|
||||
{
|
||||
private readonly DriveItemRequests _req;
|
||||
|
||||
public DriveItemRequestsAsyncTask(DriveItemRequests req)
|
||||
{
|
||||
_req = req;
|
||||
Task = Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task Task { get; private set; }
|
||||
|
||||
public DriveItemRequestsAsyncTask ForSharedDriveItemRequests(Func<Kp2aSharedDriveItemRequestBuilder, Task> action)
|
||||
{
|
||||
if (_req.SharedDriveItemRequestBuilder != null)
|
||||
{
|
||||
Task = action(_req.SharedDriveItemRequestBuilder);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
public DriveItemRequestsAsyncTask ForDriveItemRequests(Func<Kp2aDriveItemRequestBuilder, Task> action)
|
||||
{
|
||||
if (_req.DriveItemRequestBuilder != null)
|
||||
{
|
||||
Task = action(_req.DriveItemRequestBuilder);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public DriveItemRequestsAsyncTask ForCustomDriveItemRequests(Func<CustomDriveItemItemRequestBuilder, Task> action)
|
||||
{
|
||||
if (_req.CustomDriveItemRequestBuilder != null)
|
||||
{
|
||||
Task = action(_req.CustomDriveItemRequestBuilder);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public DriveItemRequestsAsyncTask ForSpecialDriveItemRequests(Func<Kp2aSpecialDriveItemRequestBuilder, Task> action)
|
||||
{
|
||||
if (_req.SpecialDriveItemRequestBuilder != null)
|
||||
{
|
||||
Task = action(_req.SpecialDriveItemRequestBuilder);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public Kp2aSharedDriveItemRequestBuilder? SharedDriveItemRequestBuilder { get; set; }
|
||||
public Kp2aSpecialDriveItemRequestBuilder? SpecialDriveItemRequestBuilder { get; set; }
|
||||
|
||||
public Kp2aDriveItemRequestBuilder? DriveItemRequestBuilder { get; set; }
|
||||
|
||||
public CustomDriveItemItemRequestBuilder? CustomDriveItemRequestBuilder { get; set; }
|
||||
|
||||
|
||||
public DriveItemRequests ForDriveItemRequests(Action<Kp2aDriveItemRequestBuilder> action)
|
||||
{
|
||||
if (DriveItemRequestBuilder != null)
|
||||
{
|
||||
action(DriveItemRequestBuilder);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public DriveItemRequests ForCustomDriveItemRequests(Action<CustomDriveItemItemRequestBuilder> action)
|
||||
{
|
||||
if (CustomDriveItemRequestBuilder != null)
|
||||
{
|
||||
action(CustomDriveItemRequestBuilder);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public DriveItemRequests ForSpecialDriveItemRequests(Action<Kp2aSpecialDriveItemRequestBuilder> action)
|
||||
{
|
||||
if (SpecialDriveItemRequestBuilder!= null)
|
||||
{
|
||||
action(SpecialDriveItemRequestBuilder );
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public DriveItemRequests ForSharedDriveItemRequests(Action<Kp2aSharedDriveItemRequestBuilder> action)
|
||||
{
|
||||
if (SharedDriveItemRequestBuilder != null)
|
||||
{
|
||||
action(SharedDriveItemRequestBuilder);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public DriveItemRequestsResult<T> ToAsyncResult<T>()
|
||||
{
|
||||
return new DriveItemRequestsResult<T>(this);
|
||||
}
|
||||
|
||||
public DriveItemRequestsAsyncTask ToAsyncTask()
|
||||
{
|
||||
return new DriveItemRequestsAsyncTask(this);
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
public async Task<DriveItemRequests> BuildPathItemAsync()
|
||||
{
|
||||
Kp2aLog.Log("buildPathItem for " + itemLocation.ToString());
|
||||
DriveItemRequests result = new DriveItemRequests();
|
||||
|
||||
if (!hasShare())
|
||||
{
|
||||
throw new Exception("Cannot get path item without share");
|
||||
@@ -228,19 +394,47 @@ namespace keepass2android.Io
|
||||
if (_specialFolder == null)
|
||||
{
|
||||
if (verbose) Kp2aLog.Log("No special folder. Use drive root.");
|
||||
pathItem = client.Me.Drive.Root;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (verbose) Kp2aLog.Log("Special folder = " + _specialFolder);
|
||||
pathItem = client.Me.Drive.Special[_specialFolder];
|
||||
}
|
||||
|
||||
if (itemLocation.LocalPath.Any())
|
||||
{
|
||||
if (verbose) Kp2aLog.Log("LocalPath = " + itemLocation.LocalPathString);
|
||||
pathItem = pathItem.ItemWithPath(itemLocation.LocalPathString);
|
||||
result.CustomDriveItemRequestBuilder = client.Drives[itemLocation.DriveId].Root
|
||||
.ItemWithPath(itemLocation.LocalPathString);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
result.DriveItemRequestBuilder = client.Drives[itemLocation.DriveId].Items["root"];
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if (verbose) Kp2aLog.Log("Special folder = " + _specialFolder);
|
||||
|
||||
result.SpecialDriveItemRequestBuilder = client.Drives[itemLocation.DriveId].Special[_specialFolder];
|
||||
if (itemLocation.LocalPath.Any())
|
||||
{
|
||||
|
||||
var child = (await result.SpecialDriveItemRequestBuilder
|
||||
.GetAsync(configuration => configuration.QueryParameters.Expand = new[] { "children" }))
|
||||
.Children.FirstOrDefault(c => c.Name == itemLocation.LocalPathString);
|
||||
|
||||
|
||||
foreach (var di in client.Drives[itemLocation.DriveId].Items.GetAsync().Result.Value)
|
||||
{
|
||||
Kp2aLog.Log("DriveItem: " + di.Name);
|
||||
|
||||
}
|
||||
|
||||
|
||||
result.DriveItemRequestBuilder = client.Drives[itemLocation.DriveId].Items[child.Id];
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -250,7 +444,8 @@ namespace keepass2android.Io
|
||||
String webUrl = itemLocation.Share.WebUrl;
|
||||
if (verbose) Kp2aLog.Log("Share WebUrl = " + webUrl);
|
||||
var encodedShareId = CalculateEncodedShareId(webUrl);
|
||||
return client.Shares[encodedShareId].Root;
|
||||
result.SharedDriveItemRequestBuilder = client.Shares[encodedShareId].Items["root"];
|
||||
return result;
|
||||
}
|
||||
/*String webUrl = itemLocation.Share.WebUrl;
|
||||
if ("".Equals(itemLocation.LocalPath) == false)
|
||||
@@ -265,11 +460,10 @@ namespace keepass2android.Io
|
||||
pathItem = client.Shares[encodedShareId].Root;
|
||||
*/
|
||||
if (verbose) Kp2aLog.Log("Using driveId=" + itemLocation.DriveId + " and item id=" + itemLocation.LocalPath.Last().Id);
|
||||
return client.Drives[itemLocation.DriveId].Items[itemLocation.LocalPath.Last().Id];
|
||||
result.DriveItemRequestBuilder = client.Drives[itemLocation.DriveId].Items[itemLocation.LocalPath.Last().Id];
|
||||
}
|
||||
|
||||
|
||||
return pathItem;
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string CalculateEncodedShareId(string webUrl)
|
||||
@@ -280,6 +474,7 @@ namespace keepass2android.Io
|
||||
return encodedShareId;
|
||||
}
|
||||
|
||||
|
||||
public bool hasShare()
|
||||
{
|
||||
return !string.IsNullOrEmpty(itemLocation?.Share?.Id);
|
||||
@@ -289,6 +484,7 @@ namespace keepass2android.Io
|
||||
{
|
||||
return itemLocation.LocalPath.Any();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private string protocolId;
|
||||
@@ -312,24 +508,24 @@ namespace keepass2android.Io
|
||||
|
||||
class GraphServiceClientWithState
|
||||
{
|
||||
public IGraphServiceClient Client { get; set; }
|
||||
public GraphServiceClient Client { get; set; }
|
||||
public DateTime TokenExpiryDate { get; set; }
|
||||
public bool RequiresUserInteraction { get; set; }
|
||||
}
|
||||
|
||||
readonly Dictionary<String /*userid*/, GraphServiceClientWithState> mClientByUser =
|
||||
readonly Dictionary<String /*userid*/, GraphServiceClientWithState> _mClientByUser =
|
||||
new Dictionary<String /*userid*/, GraphServiceClientWithState>();
|
||||
|
||||
private async Task<IGraphServiceClient> TryGetMsGraphClient(String path, bool tryConnect)
|
||||
private async Task<GraphServiceClient> TryGetMsGraphClient(String path, bool tryConnect)
|
||||
{
|
||||
|
||||
String userId = OneDrive2ItemLocation<OneDrive2PrefixContainerType>.FromString(path).User.Id;
|
||||
|
||||
string userId = OneDrive2ItemLocation<OneDrive2PrefixContainerType>.FromString(path).User.Id;
|
||||
|
||||
logDebug("TryGetMsGraphClient for " + userId);
|
||||
if (mClientByUser.ContainsKey(userId))
|
||||
if (_mClientByUser.TryGetValue(userId, out var clientWithState))
|
||||
{
|
||||
logDebug("TryGetMsGraphClient found user " + userId);
|
||||
GraphServiceClientWithState clientWithState = mClientByUser[userId];
|
||||
if (!(clientWithState.RequiresUserInteraction || (clientWithState.TokenExpiryDate < DateTime.Now) ||
|
||||
(clientWithState.Client == null)))
|
||||
{
|
||||
@@ -348,7 +544,7 @@ namespace keepass2android.Io
|
||||
if (await TryLoginSilent(path) != null)
|
||||
{
|
||||
logDebug("trying to connect ok");
|
||||
return mClientByUser[userId].Client;
|
||||
return _mClientByUser.GetValueOrDefault(userId, null).Client;
|
||||
}
|
||||
logDebug("trying to connect failed");
|
||||
}
|
||||
@@ -356,25 +552,41 @@ namespace keepass2android.Io
|
||||
return null;
|
||||
}
|
||||
|
||||
public class TokenFromAuthResultProvider : IAccessTokenProvider
|
||||
{
|
||||
public AuthenticationResult AuthenticationResult
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public async Task<string> GetAuthorizationTokenAsync(Uri uri, Dictionary<string, object>? additionalAuthenticationContext = null,
|
||||
CancellationToken cancellationToken = new CancellationToken())
|
||||
{
|
||||
return AuthenticationResult.AccessToken;
|
||||
}
|
||||
|
||||
private IGraphServiceClient BuildClient(AuthenticationResult authenticationResult)
|
||||
public AllowedHostsValidator AllowedHostsValidator { get; }
|
||||
}
|
||||
|
||||
private GraphServiceClient BuildClient(AuthenticationResult authenticationResult)
|
||||
{
|
||||
|
||||
logDebug("buildClient...");
|
||||
|
||||
|
||||
//DeviceCodeProvider authenticationProvider = new DeviceCodeProvider(_publicClientApp, Scopes);
|
||||
var authenticationProvider = new DelegateAuthenticationProvider(
|
||||
var authenticationProvider = new BaseBearerTokenAuthenticationProvider(new TokenFromAuthResultProvider() { AuthenticationResult = authenticationResult });
|
||||
/*
|
||||
(requestMessage) =>
|
||||
|
||||
{
|
||||
var access_token = authenticationResult.AccessToken;
|
||||
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", access_token);
|
||||
return Task.FromResult(0);
|
||||
});
|
||||
});*/
|
||||
|
||||
GraphServiceClientWithState clientWithState = new GraphServiceClientWithState()
|
||||
{
|
||||
Client = new GraphServiceClient(authenticationProvider),
|
||||
Client = new GraphServiceClient(new HttpClient(), authenticationProvider),
|
||||
RequiresUserInteraction = false,
|
||||
TokenExpiryDate = authenticationResult.ExpiresOn.LocalDateTime
|
||||
};
|
||||
@@ -383,7 +595,7 @@ namespace keepass2android.Io
|
||||
|
||||
if (authenticationResult.Account == null)
|
||||
throw new Exception("authenticationResult.Account == null!");
|
||||
mClientByUser[authenticationResult.Account.HomeAccountId.Identifier] = clientWithState;
|
||||
_mClientByUser[authenticationResult.Account.HomeAccountId.Identifier] = clientWithState;
|
||||
logDebug("buildClient ok.");
|
||||
return clientWithState.Client;
|
||||
}
|
||||
@@ -422,7 +634,7 @@ namespace keepass2android.Io
|
||||
}
|
||||
|
||||
|
||||
private Exception convertException(ClientException e)
|
||||
private Exception convertException(ServiceException e)
|
||||
{
|
||||
|
||||
if (e.IsMatch(GraphErrorCode.ItemNotFound.ToString()))
|
||||
@@ -436,8 +648,8 @@ namespace keepass2android.Io
|
||||
|
||||
private Exception convertException(Exception e)
|
||||
{
|
||||
if (e is ClientException)
|
||||
return convertException((ClientException)e);
|
||||
if (e is ServiceException)
|
||||
return convertException((ServiceException)e);
|
||||
if (e is AggregateException aggregateException)
|
||||
{
|
||||
foreach (var inner in aggregateException.InnerExceptions)
|
||||
@@ -464,9 +676,11 @@ namespace keepass2android.Io
|
||||
Task.Run(async () =>
|
||||
{
|
||||
PathItemBuilder pathItemBuilder = await GetPathItemBuilder(ioc.Path);
|
||||
await pathItemBuilder.getPathItem()
|
||||
.Request()
|
||||
.DeleteAsync();
|
||||
PathItemBuilder.DriveItemRequests pathItem = await pathItemBuilder.BuildPathItemAsync();
|
||||
await pathItem.ToAsyncTask()
|
||||
.ForDriveItemRequests(builder => builder.DeleteAsync())
|
||||
.ForCustomDriveItemRequests(b => b.DeleteAsync())
|
||||
.Task;
|
||||
}).Wait();
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -485,6 +699,35 @@ namespace keepass2android.Io
|
||||
return null;
|
||||
}
|
||||
|
||||
public Stream OpenFileForReadx(IOConnectionInfo ioc)
|
||||
{
|
||||
try
|
||||
{
|
||||
string filename = ioc.Path;
|
||||
|
||||
Stream? result = Task.Run(async () =>
|
||||
{
|
||||
|
||||
logDebug("openFileForRead. Path=" + filename);
|
||||
//var client = await TryGetMsGraphClient(filename, true);
|
||||
|
||||
PathItemBuilder clientAndPath = await GetPathItemBuilder(filename);
|
||||
var client = clientAndPath.client;
|
||||
var itemLocation = clientAndPath.itemLocation;
|
||||
|
||||
return await client.Drives[itemLocation.DriveId].Root.ItemWithPath(itemLocation.LocalPathString).Content
|
||||
.GetAsync();
|
||||
|
||||
}).Result;
|
||||
logDebug("ok");
|
||||
return result;
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
public Stream OpenFileForRead(IOConnectionInfo ioc)
|
||||
{
|
||||
try
|
||||
@@ -492,15 +735,23 @@ namespace keepass2android.Io
|
||||
string path = ioc.Path;
|
||||
|
||||
logDebug("openFileForRead. Path=" + path);
|
||||
Stream result = Task.Run(async () =>
|
||||
|
||||
Stream? result = Task.Run(async () =>
|
||||
{
|
||||
PathItemBuilder clientAndpath = await GetPathItemBuilder(path);
|
||||
return await clientAndpath
|
||||
.getPathItem()
|
||||
.Content
|
||||
.Request()
|
||||
.GetAsync();
|
||||
logDebug("openFileForRead. Path=" + path);
|
||||
|
||||
PathItemBuilder clientAndPath = await GetPathItemBuilder(path);
|
||||
return await (await clientAndPath.BuildPathItemAsync())
|
||||
.ToAsyncResult<Stream>()
|
||||
.ForDriveItemRequests((b) => b.Content.GetAsync())
|
||||
.ForCustomDriveItemRequests(b => b.Content.GetAsync())
|
||||
.ForSpecialDriveItemRequests((b) => b.Content.GetAsync())
|
||||
.ForSharedDriveItemRequests((b) => b.Content.GetAsync())
|
||||
.Result;
|
||||
|
||||
}).Result;
|
||||
if (result == null)
|
||||
throw new Exception("failed to open stream");
|
||||
logDebug("ok");
|
||||
return result;
|
||||
|
||||
@@ -553,20 +804,23 @@ namespace keepass2android.Io
|
||||
//for small files <2MB use the direct upload:
|
||||
if (stream.Length < 2 * 1024 * 1024)
|
||||
{
|
||||
return await
|
||||
pathItemBuilder
|
||||
.getPathItem()
|
||||
.Content
|
||||
.Request()
|
||||
.PutAsync<DriveItem>(stream);
|
||||
await
|
||||
(await pathItemBuilder
|
||||
.BuildPathItemAsync())
|
||||
.ToAsyncTask()
|
||||
.ForDriveItemRequests((b) => b.Content.PutAsync(stream))
|
||||
.ForCustomDriveItemRequests(b => b.Content.PutAsync(stream))
|
||||
.ForSpecialDriveItemRequests(b => b.Content.PutAsync(stream))
|
||||
.ForSharedDriveItemRequests(b => b.Content.PutAsync(stream))
|
||||
.Task;
|
||||
return;
|
||||
}
|
||||
|
||||
//for larger files use an upload session. This is required for 4MB and beyond, but as the docs are not very clear about this
|
||||
//limit, let's use it a bit more often to be safe.
|
||||
|
||||
var uploadProps = new DriveItemUploadableProperties
|
||||
var uploadProps = new CreateUploadSessionPostRequestBody
|
||||
{
|
||||
ODataType = null,
|
||||
AdditionalData = new Dictionary<string, object>
|
||||
{
|
||||
{ "@microsoft.graph.conflictBehavior", "replace" }
|
||||
@@ -574,11 +828,14 @@ namespace keepass2android.Io
|
||||
};
|
||||
|
||||
|
||||
var uploadSession = await pathItemBuilder
|
||||
.getPathItem()
|
||||
.CreateUploadSession(uploadProps)
|
||||
.Request()
|
||||
.PostAsync();
|
||||
var uploadSession = await (await pathItemBuilder
|
||||
.BuildPathItemAsync())
|
||||
.ToAsyncResult<UploadSession>()
|
||||
.ForSharedDriveItemRequests(b => throw new Exception("Graph SDK doesn't seem to support large file uploads on shares.")) //TODO verify
|
||||
.ForDriveItemRequests(b => b.CreateUploadSession.PostAsync(uploadProps))
|
||||
.ForCustomDriveItemRequests(b => b.CreateUploadSession.PostAsync(uploadProps))
|
||||
|
||||
.Result;
|
||||
|
||||
// Max slice size must be a multiple of 320 KiB
|
||||
int maxSliceSize = 320 * 1024;
|
||||
@@ -590,21 +847,12 @@ namespace keepass2android.Io
|
||||
throw new Exception("Failed to upload data!");
|
||||
}
|
||||
|
||||
return uploadResult.ItemResponse;
|
||||
|
||||
|
||||
|
||||
}).Wait();
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
PathItemBuilder pathItemBuilder = await GetPathItemBuilder(path);
|
||||
pathItemBuilder.verbose = true;
|
||||
pathItemBuilder.getPathItem();
|
||||
}).Wait();
|
||||
throw convertException(e);
|
||||
}
|
||||
}
|
||||
@@ -649,10 +897,11 @@ namespace keepass2android.Io
|
||||
|
||||
PathItemBuilder pathItemBuilder = await GetPathItemBuilder(parentIoc.Path);
|
||||
|
||||
return await pathItemBuilder.getPathItem()
|
||||
.Children
|
||||
.Request()
|
||||
.AddAsync(driveItem);
|
||||
return await (await pathItemBuilder.BuildPathItemAsync())
|
||||
.ToAsyncResult<DriveItem>()
|
||||
.ForDriveItemRequests(b => b.Children.PostAsync(driveItem))
|
||||
.ForCustomDriveItemRequests(b => b.Children.PostAsync(driveItem))
|
||||
.Result;
|
||||
}).Result;
|
||||
|
||||
|
||||
@@ -681,6 +930,8 @@ namespace keepass2android.Io
|
||||
PathItemBuilder pathItemBuilder = await GetPathItemBuilder(ioc.Path);
|
||||
|
||||
logDebug("listing files for " + ioc.Path);
|
||||
OneDrive2ItemLocation<OneDrive2PrefixContainerType> itemLocation = OneDrive2ItemLocation<OneDrive2PrefixContainerType>.FromString(ioc.Path);
|
||||
var client = await TryGetMsGraphClient(ioc.Path, true);
|
||||
if (!pathItemBuilder.hasShare() && !pathItemBuilder.hasOneDrivePath())
|
||||
{
|
||||
logDebug("listing shares.");
|
||||
@@ -693,31 +944,42 @@ namespace keepass2android.Io
|
||||
parentPath = parentPath.substring(getProtocolPrefix().length());
|
||||
logDebug("parent after: " + parentPath);*/
|
||||
|
||||
IDriveItemChildrenCollectionPage itemsPage = await pathItemBuilder.getPathItem()
|
||||
.Children
|
||||
.Request()
|
||||
.GetAsync();
|
||||
while (true)
|
||||
{
|
||||
IList<DriveItem> items = itemsPage.CurrentPage;
|
||||
if (!items.Any())
|
||||
return result;
|
||||
var driveItems = await GetDriveItems(pathItemBuilder);
|
||||
|
||||
foreach (DriveItem i in items)
|
||||
if (driveItems != null)
|
||||
foreach (DriveItem? i in driveItems)
|
||||
{
|
||||
var e = GetFileDescription(pathItemBuilder.itemLocation.BuildLocalChildLocation(i.Name, i.Id, i.ParentReference?.DriveId), i);
|
||||
var e = GetFileDescription(itemLocation.BuildLocalChildLocation(i.Name, i.Id, i.ParentReference?.DriveId), i);
|
||||
result.Add(e);
|
||||
}
|
||||
var nextPageReqBuilder = itemsPage.NextPageRequest;
|
||||
if (nextPageReqBuilder == null)
|
||||
|
||||
return result;
|
||||
itemsPage = Task.Run(async () => await nextPageReqBuilder.GetAsync()).Result;
|
||||
|
||||
}
|
||||
|
||||
private async Task<List<DriveItem?>> GetDriveItems(
|
||||
PathItemBuilder pathItemBuilder)
|
||||
{
|
||||
var pathItem = await pathItemBuilder.BuildPathItemAsync();
|
||||
if (pathItem.SpecialDriveItemRequestBuilder != null)
|
||||
{
|
||||
return pathItem.SpecialDriveItemRequestBuilder
|
||||
.GetAsync(configuration => configuration.QueryParameters.Expand = new[] { "children" }).Result
|
||||
.Children;
|
||||
}
|
||||
var response = await pathItem
|
||||
.ToAsyncResult<DriveItemCollectionResponse>()
|
||||
.ForDriveItemRequests(b => b.Children.GetAsync())
|
||||
.ForCustomDriveItemRequests(b => b.Children.GetAsync())
|
||||
.ForSharedDriveItemRequests(b =>
|
||||
throw new Java.Lang.Exception("TODO listing share children not implemented"))
|
||||
|
||||
.Result;
|
||||
return response.Value;
|
||||
}
|
||||
|
||||
|
||||
private FileDescription GetFileDescription(OneDrive2ItemLocation<OneDrive2PrefixContainerType> path, DriveItem i)
|
||||
private FileDescription GetFileDescription(OneDrive2ItemLocation<OneDrive2PrefixContainerType> path, DriveItem? i)
|
||||
{
|
||||
FileDescription e = new FileDescription();
|
||||
if (i.Size != null)
|
||||
@@ -764,9 +1026,13 @@ namespace keepass2android.Io
|
||||
return rootEntry;
|
||||
}
|
||||
|
||||
IDriveItemRequestBuilder pathItem = pathItemBuilder.getPathItem();
|
||||
|
||||
DriveItem item = await pathItem.Request().GetAsync();
|
||||
var driveReq = await pathItemBuilder.BuildPathItemAsync();
|
||||
DriveItem? item = await driveReq.ToAsyncResult<DriveItem>()
|
||||
.ForDriveItemRequests(b => b.GetAsync())
|
||||
.ForCustomDriveItemRequests(b => b.GetAsync())
|
||||
.ForSharedDriveItemRequests(b => b.GetAsync())
|
||||
.ForSpecialDriveItemRequests(b => b.GetAsync())
|
||||
.Result;
|
||||
return GetFileDescription(pathItemBuilder.itemLocation, item);
|
||||
}
|
||||
|
||||
@@ -966,7 +1232,7 @@ namespace keepass2android.Io
|
||||
};
|
||||
|
||||
|
||||
mClientByUser[account.HomeAccountId.Identifier] = clientWithState;
|
||||
_mClientByUser[account.HomeAccountId.Identifier] = clientWithState;
|
||||
logDebug("ui required");
|
||||
return null;
|
||||
}
|
||||
@@ -1029,41 +1295,48 @@ namespace keepass2android.Io
|
||||
}
|
||||
|
||||
|
||||
private async Task<List<FileDescription>> ListShares(OneDrive2ItemLocation<OneDrive2PrefixContainerType> parentPath, IGraphServiceClient client)
|
||||
private async Task<List<FileDescription>> ListShares(OneDrive2ItemLocation<OneDrive2PrefixContainerType> parentPath, GraphServiceClient client)
|
||||
{
|
||||
|
||||
List<FileDescription> result = new List<FileDescription>();
|
||||
List<FileDescription> result = [];
|
||||
|
||||
|
||||
DriveItem root = await client.Me.Drive.Root.Request().GetAsync();
|
||||
FileDescription myEntry = GetFileDescription(parentPath.BuildShare("me","me","me", root.ParentReference?.DriveId), root);
|
||||
myEntry.DisplayName = MyOneDriveDisplayName;
|
||||
|
||||
result.Add(myEntry);
|
||||
var drives = (await client.Me.Drives.GetAsync()).Value;
|
||||
if (drives != null)
|
||||
{
|
||||
drives.ForEach(drive =>
|
||||
{
|
||||
var e = new FileDescription()
|
||||
{
|
||||
DisplayName = drive.Name ?? drive.DriveType ?? "(unnamed drive)",
|
||||
IsDirectory = true,
|
||||
CanRead = true,
|
||||
CanWrite = true,
|
||||
Path = parentPath.BuildShare("me","me","me", drive.Id).ToString()
|
||||
};
|
||||
result.Add(e);
|
||||
});
|
||||
}
|
||||
|
||||
if (!CanListShares)
|
||||
return result;
|
||||
|
||||
/*
|
||||
Not working. See https://stackoverflow.com/questions/79374963/list-shared-folders-with-graph-sdk.
|
||||
var sharedWithMeResponse = await client.Drives[parentPath.DriveId].SharedWithMe.GetAsSharedWithMeGetResponseAsync();
|
||||
|
||||
|
||||
IDriveSharedWithMeCollectionPage sharedWithMeCollectionPage = await client.Me.Drive.SharedWithMe().Request().GetAsync();
|
||||
|
||||
while (true)
|
||||
foreach (DriveItem i in sharedWithMeResponse?.Value ?? [])
|
||||
{
|
||||
IList<DriveItem> sharedWithMeItems = sharedWithMeCollectionPage.CurrentPage;
|
||||
if (!sharedWithMeItems.Any())
|
||||
break;
|
||||
|
||||
foreach (DriveItem i in sharedWithMeItems)
|
||||
var oneDrive2ItemLocation = parentPath.BuildShare(i.Id, i.Name, i.WebUrl, i.ParentReference?.DriveId);
|
||||
FileDescription sharedFileEntry = new FileDescription()
|
||||
{
|
||||
FileDescription sharedFileEntry = GetFileDescription(parentPath.BuildShare(i.Id, i.Name, i.WebUrl, i.ParentReference?.DriveId), i);
|
||||
CanWrite = true, CanRead = true, DisplayName = i.Name,
|
||||
IsDirectory = true,
|
||||
Path = oneDrive2ItemLocation.ToString()
|
||||
};
|
||||
result.Add(sharedFileEntry);
|
||||
|
||||
}
|
||||
var b = sharedWithMeCollectionPage.NextPageRequest;
|
||||
if (b == null) break;
|
||||
sharedWithMeCollectionPage = await b.GetAsync();
|
||||
}
|
||||
}*/
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1071,29 +1344,19 @@ namespace keepass2android.Io
|
||||
|
||||
public abstract bool CanListShares { get; }
|
||||
|
||||
DriveItem TryFindFile(PathItemBuilder parent, string filename)
|
||||
async Task<DriveItem?> TryFindFileAsync(PathItemBuilder parent, string filename)
|
||||
{
|
||||
IDriveItemChildrenCollectionPage itemsPage = Task.Run(async () => await parent.getPathItem()
|
||||
.Children
|
||||
.Request()
|
||||
.GetAsync()).Result;
|
||||
while (true)
|
||||
{
|
||||
IList<DriveItem> items = itemsPage.CurrentPage;
|
||||
if (!items.Any())
|
||||
return null;
|
||||
|
||||
foreach (DriveItem i in items)
|
||||
var driveItems = await GetDriveItems(parent);
|
||||
|
||||
if (driveItems != null)
|
||||
foreach (DriveItem? i in driveItems)
|
||||
{
|
||||
if (i.Name == filename)
|
||||
return i;
|
||||
}
|
||||
var nextPageReqBuilder = itemsPage.NextPageRequest;
|
||||
if (nextPageReqBuilder == null)
|
||||
return null;
|
||||
itemsPage = Task.Run(async () => await nextPageReqBuilder.GetAsync()).Result;
|
||||
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
@@ -1116,7 +1379,7 @@ namespace keepass2android.Io
|
||||
PathItemBuilder pathItemBuilder = await GetPathItemBuilder(parent);
|
||||
|
||||
//see if such a file exists already:
|
||||
var item = TryFindFile(pathItemBuilder, newFilename);
|
||||
var item = await TryFindFileAsync(pathItemBuilder, newFilename);
|
||||
if (item != null)
|
||||
{
|
||||
return pathItemBuilder.itemLocation.BuildLocalChildLocation(item.Name, item.Id, item.ParentReference?.DriveId)
|
||||
@@ -1125,12 +1388,14 @@ namespace keepass2android.Io
|
||||
//doesn't exist. Create:
|
||||
logDebug("building request for " + pathItemBuilder.itemLocation);
|
||||
|
||||
DriveItem res = await pathItemBuilder.getPathItem()
|
||||
|
||||
.ItemWithPath(newFilename)
|
||||
.Content
|
||||
.Request()
|
||||
.PutAsync<DriveItem>(new MemoryStream());
|
||||
var emptyStream = new MemoryStream();
|
||||
var driveItemReq = await pathItemBuilder.BuildPathItemAsync();
|
||||
DriveItem? res = await driveItemReq
|
||||
.ToAsyncResult<DriveItem>()
|
||||
.ForDriveItemRequests(b => b.Content.PutAsync(emptyStream))
|
||||
.ForCustomDriveItemRequests(b => b.Content.PutAsync(emptyStream))
|
||||
.ForSharedDriveItemRequests(b => b.Content.PutAsync(emptyStream))
|
||||
.Result;
|
||||
|
||||
return pathItemBuilder.itemLocation.BuildLocalChildLocation(res.Name, res.Id, res.ParentReference?.DriveId)
|
||||
.ToString();
|
||||
|
@@ -11,9 +11,10 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentFTP" Version="51.1.0" />
|
||||
<PackageReference Include="MegaApiClient" Version="1.10.4" />
|
||||
<PackageReference Include="Microsoft.Graph" Version="1.21.0" />
|
||||
<PackageReference Include="Microsoft.Graph" Version="5.68.0" />
|
||||
<PackageReference Include="Microsoft.Graph.Auth" Version="1.0.0-preview.7" />
|
||||
<PackageReference Include="Microsoft.Identity.Client" Version="4.49.1" />
|
||||
<PackageReference Include="Microsoft.Identity.Client" Version="4.67.1" />
|
||||
<PackageReference Include="Xamarin.AndroidX.Browser" Version="1.8.0" />
|
||||
<PackageReference Include="Xamarin.AndroidX.Core" Version="1.13.1.5" />
|
||||
<PackageReference Include="Xamarin.Google.Android.Material" Version="1.11.0.3" />
|
||||
</ItemGroup>
|
||||
|
Reference in New Issue
Block a user