diff --git a/src/Kp2aBusinessLogic/Io/OneDrive2FileStorage.cs b/src/Kp2aBusinessLogic/Io/OneDrive2FileStorage.cs index 132981dd..e73c7bea 100644 --- a/src/Kp2aBusinessLogic/Io/OneDrive2FileStorage.cs +++ b/src/Kp2aBusinessLogic/Io/OneDrive2FileStorage.cs @@ -1125,9 +1125,57 @@ namespace keepass2android.Io } } + public static async Task GetOrCreateAppRootAsync(GraphServiceClient client, string dummyFileName = "welcome_at_kp2a.txt") + { - private async Task> ListShares(OneDrive2ItemLocation parentPath, GraphServiceClient client) + try + { + return await client.RequestAdapter.SendAsync( + new Microsoft.Graph.Drives.Item.Items.Item.DriveItemItemRequestBuilder( + new Dictionary { + { "drive%2Did", "me" }, + { "driveItem%2Did", "special/approot" } + }, + client.RequestAdapter + ).ToGetRequestInformation(), + static (p) => DriveItem.CreateFromDiscriminatorValue(p) + ); + } + catch (Microsoft.Kiota.Abstractions.ApiException ex) when (ex.ResponseStatusCode == 404) + { + // App folder doesn’t exist yet → create it by uploading a dummy file + using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes("init")); + + var uploadRequest = new RequestInformation + { + HttpMethod = Method.PUT, + UrlTemplate = "{+baseurl}/me/drive/special/approot:/{filename}:/content", + PathParameters = new Dictionary + { + { "baseurl", client.RequestAdapter.BaseUrl }, + { "filename", dummyFileName } + }, + Content = stream + }; + + var uploadedItem = await client.RequestAdapter.SendAsync( + uploadRequest, + DriveItem.CreateFromDiscriminatorValue + ); + + var parentId = uploadedItem.ParentReference.Id; + + var parentItemRequest = new DriveItemRequestBuilder( + $"{client.RequestAdapter.BaseUrl}/me/drive/items/{parentId}", + client.RequestAdapter + ); + + return await parentItemRequest.GetAsync(); + } + } + + protected virtual async Task> ListShares(OneDrive2ItemLocation parentPath, GraphServiceClient client) { List result = []; @@ -1345,6 +1393,8 @@ namespace keepass2android.Io } } + + protected override async Task GetSpecialFolder( OneDrive2ItemLocation itemLocation, GraphServiceClient client) @@ -1363,7 +1413,7 @@ namespace keepass2android.Io Console.WriteLine(e); throw; } - + } return _specialFolderIdByDriveId[itemLocation.DriveId]; @@ -1378,8 +1428,55 @@ namespace keepass2android.Io { return drive.Name ?? MyOneDriveDisplayName; } + public static async Task GetOrCreateAppRootAsync(GraphServiceClient client, string dummyFileName = "welcome_at_kp2a_app_folder.txt") + { + try + { + await client.RequestAdapter.SendAsync( + new Microsoft.Graph.Drives.Item.Items.Item.DriveItemItemRequestBuilder( + new Dictionary { + { "drive%2Did", "me" }, + { "driveItem%2Did", "special/approot" } + }, + client.RequestAdapter + ).ToGetRequestInformation(), + static (p) => DriveItem.CreateFromDiscriminatorValue(p) + ); + //if this is successful, approot seems to exist + } + catch (Microsoft.Kiota.Abstractions.ApiException ex) when (ex.ResponseStatusCode == 404) + { + // App folder doesn’t exist yet → create it by uploading a dummy file + using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes("init")); + + var uploadRequest = new RequestInformation + { + HttpMethod = Method.PUT, + UrlTemplate = "{+baseurl}/me/drive/special/approot:/{filename}:/content", + PathParameters = new Dictionary + { + { "baseurl", client.RequestAdapter.BaseUrl }, + { "filename", dummyFileName } + }, + Content = stream + }; + + await client.RequestAdapter.SendAsync( + uploadRequest, + DriveItem.CreateFromDiscriminatorValue + ); + + } + } + + protected override async Task> ListShares(OneDrive2ItemLocation parentPath, GraphServiceClient client) + { + await GetOrCreateAppRootAsync(client); + return await base.ListShares(parentPath, client); + } + public override bool CanListShares { get { return false; } } protected override string MyOneDriveDisplayName => "Keepass2Android App Folder"; }